Bài giảng Lập trình hướng đối tượng (Bản đầy đủ)

pdf 801 trang ngocly 2380
Bạn đang xem 20 trang mẫu của tài liệu "Bài giảng Lập trình hướng đối tượng (Bản đầy đủ)", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên

Tài liệu đính kèm:

  • pdfbai_giang_lap_trinh_huong_doi_tuong_ban_day_du.pdf

Nội dung text: Bài giảng Lập trình hướng đối tượng (Bản đầy đủ)

  1. CHƯƠNG 1 GIỚ I THIÊỤ VỀ LÂP̣ TRIǸ H HƯỚ NG ĐỐ I TƯƠṆ G 1.1 LẬP TRIǸ H HƯỚ NG ĐỐ I TƯỢNG (oop) Là gi ̀ ? Lâp̣ triǹ h hướng đối tươṇ g (Object- Oriented Programming, viết tắt là OOP) là môṭ phương pháp mới trên bước đường tiến hóa của viêc̣ lâp̣ triǹ h máy tính, nhăm̀ làm cho chương triǹ h trở nên linh hoaṭ, tin câỵ và dễ phát triển. Tuy nhiên để hiểu đươc̣ OOP là gi,̀ chúng ta haỹ bắt đâù từ lic̣ h sử của quá triǹ h lâp̣ triǹ h – xem xét OOP đa ̃ tiến hóa như thế nào. 1.1.1 Lâp̣ triǹ h tuyến tính
  2. Máy tính đâù tiên đươc̣ lâp̣ triǹ h băǹ g
  3. ma ̃ nhi ̣ phân, sử duṇ g các công tắt cơ khí để nap̣ chương triǹ h. Cùng với sư ̣ xuất hiêṇ của các thiết bi ̣lưu trữ lớn và bô ̣ nhớ máy tính có dung lươṇ g lớn nên các ngôn ngữ lâp̣ triǹ h cấp cao đâù tiên đươc̣ đưa vào sử duṇ g . Thay vi ̀ phải suy nghi ̃ trên môṭ daỹ các bit và byte, lâp̣ triǹ h viên có thể viết môṭ loaṭ lêṇ h gâǹ với tiếng Anh và sau đó chương triǹ h dic̣ h thành ngôn ngữ máy. Các ngôn ngữ lâp̣ triǹ h cấp cao đâù tiên đươc̣ thiết kế để lâp̣ các chương triǹ h làm các công viêc̣ tương đối đơn giản như tính toán. Các chương triǹ h ban đâù chủ yếu liên quan đến tính toán và không đòi hỏi gì nhiều ở ngôn ngữ lâp̣ triǹ h. Hơn nữa phâǹ lớn các chương triǹ h này tương đối ngắn, thường ít hơn 100 dòng.
  4. Khi khả năng của máy tính tăng lên thi ̀ khả năng để triển khai các chương triǹ h phứ c tap̣ hơn cũng tăng lên. Các ngôn ngữ lâp̣ triǹ h ngày trước không còn thích hơp̣ đối với viêc̣ lâp̣ triǹ h đòi hỏi cao hơn. Các phương tiêṇ câǹ thiết để sử duṇ g laị các phâǹ ma ̃ chương triǹ h đa ̃ viết hâù như không có trong ngôn ngữ lâp̣ triǹ h tuyến tính. Thâṭ ra, môṭ đoaṇ lêṇ h thường phải đươc̣ chép lăp̣ laị mỗi khi chúng ta dùng trong nhiều chương triǹ h do đó chương triǹ h dài dòng, logic của chương triǹ h khó hiểu. Chương triǹ h đươc̣ điều khiển để nhảy đến nhiều chỗ mà thường không có sự giải thích rõ ràng, làm thế nào để chương triǹ h đến chỗ câǹ thiết hoăc̣ taị sao như vâỵ . Ngôn ngữ lâp̣ triǹ h tuyến tính không có khả
  5. năng kiểm soát phaṃ vi nhiǹ thấy của các dữ liêụ . Moị dữ liêụ trong chương triǹ h đều là dữ liêụ toàn cuc̣ nghiã là chúng có thể bi ̣ sử a đổi ở bất kỳ phâǹ nào của chương triǹ h. Viêc̣ dò tim̀ các thay đổi không mong muốn đó của các phâǹ tử dữ liêụ trong môṭ daỹ ma ̃ lêṇ h dài và vòng vèo đa ̃ từng làm cho các lâp̣ triǹ h viên rất mất thời gian. 1.1.2 Lâp̣ triǹ h cấu trúc: Rõ ràng là các ngôn ngữ mới với các tính năng mới câǹ phải đươc̣ phát triển để có thể taọ ra các ứ ng duṇ g tinh vi hơn. Vào cuối các năm trong 1960 và 1970, ngôn ngữ lâp̣ triǹ h có cấu trúc ra đời. Các chương triǹ h có cấu trúc đươc̣ tổ chứ c theo các công viêc̣ mà chúng thưc̣ hiêṇ . Về bản chất, chương triǹ h chia nhỏ thành các
  6. chương triǹ h con riêng rẽ (còn goị là hàm hay thủ tuc̣ ) thưc̣ hiêṇ các công viêc̣ rời rac̣ trong quá triǹ h lớn hơn, phứ c tap̣ hơn. Các hàm này đươc̣ giữ càng đôc̣ lâp̣ với nhau càng nhiều càng tốt, mỗi hàm có dữ liêụ và logic riêng.Thông tin đươc̣ chuyển giao giữa các hàm thông qua các tham số, các hàm có thể có các biến cuc̣ bô ̣ mà không môṭ ai năm̀ bên ngoài phaṃ vi của hàm laị có thể truy xuất đươc̣ chúng. Như vâỵ , các hàm có thể đươc̣ xem là các chương triǹ h con đươc̣ đăṭ chung với nhau để xây dưṇ g nên môṭ ứ ng duṇ g. Muc̣ tiêu là làm sao cho viêc̣ triển khai các phâǹ mềm dễ dàng hơn đối với các lâp̣ triǹ h viên mà vâñ cải thiêṇ đươc̣ tính tin câỵ và dễ bảo quản chương triǹ h. Môṭ chương triǹ h có cấu trúc đươc̣ hiǹ h thành
  7. băǹ g cách bẻ gaỹ các chứ c năng cơ bản của chương triǹ h thành các mảnh nhỏ mà sau đó trở thành các hàm. Băǹ g cách cô lâp̣ các công viêc̣ vào trong các hàm, chương triǹ h có cấu trúc có thể làm giảm khả năng của môṭ hàm này ảnh hưởng đến môṭ hàm khác. Viêc̣ này cũng làm cho viêc̣ tách các vấn đề trở nên dễ dàng hơn. Sự gói goṇ này cho phép chúng ta có thể viết các chương triǹ h sáng sủa hơn và giữ đươc̣ điều khiển trên từng hàm. Các biến toàn cuc̣ không còn nữa và đươc̣ thay thế băǹ g các tham số và biến cuc̣ bô ̣ có phaṃ vi nhỏ hơn và dễ kiểm soát hơn. Cách tổ chứ c tốt hơn này nói lên răǹ g chúng ta có khả năng quản lý logic của cấu trúc chương triǹ h, làm cho viêc̣ triển khai và bảo dưỡng chương triǹ h nhanh hơn và hữu hiêṇ hơn và hiêụ quả
  8. hơn. Môṭ khái niêṃ lớn đa ̃ đươc̣ đưa ra trong lâp̣ triǹ h có cấu trúc là sư ̣trừu tươṇ g hó a (Abstraction). Sư ̣ trừu tươṇ g hóa có thể xem như khả năng quan sát môṭ sư ̣ viêc̣ mà không câǹ xem xét đến các chi tiết bên trong của nó. Trong môṭ chương triǹ h có cấu trúc, chúng ta chỉ câǹ biết môṭ hàm đã cho có thể làm đươc̣ môṭ công viêc̣ cu ̣ thể gi ̀ là đủ. Còn làm thế nào mà công viêc̣ đó laị thưc̣ hiêṇ đươc̣ là không quan troṇ g, chừng nào hàm còn tin câỵ đươc̣ thi ̀ còn có thể dùng nó mà không câǹ phải biết nó thưc̣ hiêṇ đúng đắn chứ c năng của miǹ h như thế nào. Điều này goị là sư ̣ trừu tươṇ g hó a theo chứ c năng (Functional abstraction) và là nền tảng của lâp̣ triǹ h có cấu trúc. Ngày
  9. nay, các kỹ thuâṭ thiết kế và lâp̣ triǹ h có cấu trúc đươc̣ sử rôṇ g raĩ . Gâǹ như moị ngôn ngữ lâp̣ triǹ h đều có các phương tiêṇ câǹ thiết để cho phép lâp̣ triǹ h có cấu trúc. Chương triǹ h có cấu trúc dễ viết, dễ bảo dưỡng hơn các chương triǹ h không cấu trúc. Sư ̣ nâng cấp như vâỵ cho các kiểu dữ liêụ trong các ứ ng duṇ g mà các lâp̣ triǹ h viên đang viết cũng đang tiếp tuc̣ diễn ra. Khi đô ̣ phứ c tap̣ của môṭ chương triǹ h tăng lên, sư ̣ phu ̣ thuôc̣ của nó vào các kiểu dữ liêụ cơ bản mà nó xử lý cũng tăng theo. Vấn đề trở rõ ràng là cấu trúc dữ liêụ trong chương triǹ h quan troṇ g chẳng kém gi ̀ các phép toán thưc̣ hiêṇ trên chúng. Điều này càng trở rõ ràng hơn khi kích thước của chương triǹ h càng tăng. Các kiểu dữ liêụ đươc̣ xử lý trong nhiều hàm khác nhau bên
  10. trong môṭ chương triǹ h có cấu trúc. Khi có sư ̣ thay đổi trong các dữ liêụ này thi ̀ cũng câǹ phải thưc̣ hiêṇ cả các thay đổi ở moị nơi có các thao tác tác đôṇ g trên chúng. Đây có thể là môṭ công viêc̣ tốn thời gian và kém hiêụ quả đối với các chương triǹ h có hàng ngàn dòng lêṇ h và hàng trăm hàm trở lên. Môṭ yếu điểm nữa của viêc̣ lâp̣ triǹ h có cấu trúc là khi có nhiều lâp̣ triǹ h viên làm viêc̣ theo nhóm cùng môṭ ứ ng duṇ g nào đó. Trong môṭ chương triǹ h có cấu trúc, các lâp̣ triǹ h viên đươc̣ phân công viết môṭ tâp̣ hơp̣ các hàm và các kiểu dữ liêụ . Vi ̀ có nhiều lâp̣ triǹ h viên khác nhau quản lý các hàm riêng, có liên quan đến các kiểu dữ liêụ dùng chung nên các thay đổi mà lâp̣
  11. triǹ h viên taọ ra trên môṭ phâǹ tử dữ liêụ sẽ làm ảnh hưởng đến công viêc̣ của tất cả các người còn laị trong nhóm. Măc̣ dù trong bối cảnh làm viêc̣ theo nhóm, viêc̣ viết các chương triǹ h có cấu trúc thi ̀ dễ dàng hơn nhưng sai sót trong viêc̣ trao đổi thông tin giữa các thành viên trong nhóm có thể dâñ tới hâụ quả là mất rất nhiều thời gian để sử a chữa chương triǹ h. 1.1.3 Sư ̣ trừu tươṇ g hóa dữ liêụ : Sư ̣ trừu tươṇ g hó a dữ liêụ (Data abstraction) tác đôṇ g trên các dữ liêụ cũng tương tư ̣ như sư ̣ trừu tươṇ g hóa theo chứ c năng. Khi có trừu tươṇ g hóa dữ liêụ , các cấu trúc dữ liêụ và các phâǹ tử có thể đươc̣ sử duṇ g mà không câǹ bâṇ tâm đến các chi tiết cu ̣ thể. Chẳng haṇ như các số dấu chấm đôṇ g đa ̃ đươc̣ trừu tươṇ g hóa trong tất cả các ngôn ngữ lâp̣ triǹ h, Chúng
  12. ta không câǹ quan tâm cách biểu diễn nhi ̣ phân chính xác nào cho số dấu chấm đôṇ g khi gán môṭ giá tri,̣ cũng không câǹ biết tính bất thường của phép nhân nhi ̣phân khi nhân các giá tri ̣dấu chấm đôṇ g. Điều quan troṇ g là các số dấu chấm đôṇ g hoaṭ đôṇ g đúng đắn và hiểu được. Sự trừu tươṇ g hóa dữ liêụ giúp chúng ta không phải bâṇ tâm về các chi tiết không câǹ thiết. Nếu lâp̣ triǹ h viên phải hiểu biết về tất cả các khía caṇ h của vấn đề, ở moị lúc và về tất cả các hàm của chương triǹ h thì chỉ ít hàm mới đươc̣ viết ra, may mắn thay trừu tươṇ g hóa theo dữ liêụ đa ̃ tồn taị săñ trong moị ngôn ngữ lâp̣ triǹ h đối với các dữ liêụ phứ c tap̣ như số dấu chấm đôṇ g. Tuy nhiên chỉ mới gâǹ đây, người ta mới
  13. phát triển các ngôn ngữ cho phép chúng ta điṇ h nghiã các kiểu dữ liêụ trừu tươṇ g riêng. 1.1.4 Lâp̣ triǹ h hướng đối tươṇ g: Khái niêṃ hướng đối tươṇ g đươc̣ xây dưṇ g trên nền tảng của khái niêṃ lâp̣ triǹ h có cấu trúc và sư ̣ trừu tươṇ g hóa dữ liêụ . Sư ̣ thay đổi căn bản ở chỗ, môṭ chương triǹ h hướng đối tươṇ g đươc̣ thiết kế xoay quanh dữ liêụ mà chúng ta có thể làm viêc̣ trên đó, hơn là theo bản thân chứ c năng của chương triǹ h. Điều này hoàn toàn tự nhiên môṭ khi chúng ta hiểu răǹ g muc̣ tiêu của chương triǹ h là xử lý dữ liêụ . Suy cho cùng, công viêc̣ mà máy tính thưc̣ hiêṇ vâñ thường đươc̣ goị là xử lý dữ liêụ . Dữ liêụ và thao tác liên kết với nhau ở môṭ mứ c cơ bản (còn có thể goị là mứ c thấp), mỗi thứ đều đòi hỏi ở thứ kia có muc̣ tiêu cu ̣ thể,
  14. các chương triǹ h hướng đối tươṇ g làm tường minh mối quan hệ này. Lâp̣ triǹ h hướng đối tươṇ g liên kết cấu trúc dữ liêụ với các thao tác, theo cách mà tất cả thường nghi ̃ về thế giới quanh miǹ h. Chúng ta thường gắn môṭ số các hoaṭ đôṇ g cu ̣ thể với môṭ loaị hoaṭ đôṇ g nào đó và đăṭ các giả thiết của miǹ h trên các quan hệ đ ó . Ví du1̣ .1: Chúng ta biết răǹ g môṭ chiếc xe có các bánh xe, di chuyển đươc̣ và có thể đổi hướng của nó băǹ g cách queọ tay lái. Tương tư ̣ như thế, môṭ cái cây là môṭ loaị thưc̣ vâṭ có thân gỗ và lá. Môṭ chiếc xe không phải là môṭ cái cây, mà cái cây không phải là môṭ chiếc xe, chúng ta có thể giả thiết răǹ g cái mà chúng ta có thể làm đươc̣ với môṭ
  15. chiếc xe thi ̀ không thể làm đươc̣ với môṭ cái cây. Chẳng haṇ , thâṭ là vô nghiã khi muốn lái môṭ cái cây, còn chiếc xe thi ̀ laị chẳng lớn thêm đươc̣ khi chúng ta tưới nước cho nó. Lâp̣ triǹ h hướng đối tươṇ g cho phép chúng ta sử duṇ g các quá triǹ h suy nghi ̃ như vâỵ với các khái niêṃ trừu tươṇ g đươc̣ sử duṇ g trong các chương triǹ h máy tính. Môṭ mâũ tin (record) nhân sư ̣ có thể đươc̣ đoc̣ ra, thay đổi và lưu trữ laị; còn số phứ c thi ̀ có thể đươc̣ dùng trong các tính toán. Tuy vâỵ không thể nào laị viết môṭ số phứ c vào tâp̣ tin làm mâũ tin nhân sư ̣ và ngươc̣ laị hai mâũ tin nhân sự laị không thể côṇ g với nhau đươc̣ . Môṭ chương triǹ h hướng đối tươṇ g sẽ xác điṇ h đăc̣ điểm và hành vi cu ̣ thể của các kiểu
  16. dữ liêụ , điều đó cho phép chúng ta biết môṭ cách chính xác răǹ g chúng ta có thể có đươc̣ những gi ̀ ở các kiểu dữ liêụ khác nhau. Chúng ta còn có thể taọ ra các quan hệ giữa các kiểu dữ liêụ tương tư ̣ nhưng khác nhau trong môṭ chương triǹ h hướng đối tươṇ g. Người ta thường tư ̣ nhiên phân loaị ra moị thứ , thường đăṭ mối liên hê ̣ giữa các khái niêṃ mới với các khái niêṃ đã có, và thường có thể thưc̣ hiêṇ suy diễn giữa chúng trên các quan hê ̣ đó. Haỹ quan niêṃ thế giới theo kiểu cấu trúc cây, với các mứ c xây dưṇ g chi tiết hơn kế tiếp nhau cho các thế hê ̣sau so với các thế hê ̣trước. Đây là phương pháp hiêụ quả để tổ chứ c thế giới quanh chúng ta. Các chương triǹ h hướng đối tươṇ g cũng làm viêc̣ theo môṭ phương thứ c tương tư,̣ trong đó chúng cho
  17. phép xây dưṇ g các các cơ cấu dữ liêụ và thao tác mới dưạ trên các cơ cấu có săñ , mang theo các tính năng của các cơ cấu nền mà chúng dưạ trên đó, trong khi vâñ thêm vào các tính năng mới. Lâp̣ triǹ h hướng đối tươṇ g cho phép chúng ta tổ chứ c dữ liêụ trong chương triǹ h theo môṭ cách tương tự như các nhà sinh hoc̣ tổ chứ c các loaị thưc̣ vâṭ khác nhau. Theo cách nói lâp̣ triǹ h đối tươṇ g, xe hơi, cây cối, các số phứ c, các quyển sách đều đươc̣ goị là các lớ p (Class). Môṭ lớp là môṭ bản mâũ mô tả các thông tin cấu trúc dữ liêụ , lâñ các thao tác hơp̣ lệ của các phâǹ tử dữ liêụ . Khi môṭ phâǹ tử dữ liêụ đươc̣ khai báo là phâǹ tử của môṭ lớp thi ̀ nó đươc̣ goị là môṭ đố i tươṇ g
  18. (Object). Các hàm đươc̣ điṇ h nghiã hơp̣ lê ̣ trong môṭ lớp đươc̣ goị là các phương thứ c (Method) và chúng là các hàm duy nhất có thể xử lý dữ liêụ của các đối tươṇ g của lớp đó. Môṭ thưc̣ thể (Instance) là môṭ vâṭ thể có thưc̣ bên trong bô ̣ nhớ, thưc̣ chất đó là môṭ đối tươṇ g (nghiã là môṭ đối tươṇ g đươc̣ cấp phát vùng nhớ). Mỗi môṭ đối tươṇ g có riêng cho miǹ h môṭ bản sao các phâǹ tử dữ liêụ của lớp còn goị là các biến thưc̣ thể (Instance variable). Các phương thứ c điṇ h nghiã trong môṭ lớp có thể đươc̣ goị bởi các đối tươṇ g của lớp đó. Điều này đươc̣ goị là gử i môṭ thông điêp̣ (Message) cho đối tươṇ g. Các thông điêp̣ này phu ̣ thuôc̣ vào đối tươṇ g, chỉ đối tươṇ g nào nhâṇ thông điêp̣ mới phải làm viêc̣ theo thông điêp̣
  19. đó. Các đối tươṇ g đều đôc̣ lâp̣ với nhau vì vâỵ các thay đổi trên các biến thể hiêṇ của đối tươṇ g này không ảnh hưởng gi ̀ trên các biến thể hiêṇ của các đối tươṇ g khác và viêc̣ gử i thông điêp̣ cho môṭ đối tươṇ g này không ảnh hưởng gi ̀ đến các đối tươṇ g khác. 1.2 MỘT SỐ KHÁ I NIÊṂ MỚ I TRONG LẬP TRIǸ H HƯỚ NG ĐỐ I TƯỢNG Trong phâǹ này, chúng ta tim̀ hiểu các khái niêṃ như sư ̣ đóng gói, tính kế thừa và tính đa hiǹ h. Đây là các khái niêṃ căn bản, là nền tảng tư tưởng của lâp̣ triǹ h hướng đối tươṇ g. Hiểu đươc̣ khái niêṃ này, chúng ta bước đâù tiếp câṇ với phong cách lâp̣ triǹ h mới, phong cách lâp̣ triǹ h dưạ vào đối tươṇ g làm nền tảng mà trong đó quan điểm che dấu thông tin thông qua sư
  20. đóng gói là quan điểm trung tâm của vấn đề. 1.2.1 Sư ̣ đóng gói (Encapsulation) Sư ̣ đóng gói là cơ chế ràng buôc̣ dữ liêụ và thao tác trên dữ liêụ đó thành môṭ thể thống nhất, tránh đươc̣ các tác đôṇ g bất ngờ từ bên ngoài. Thể thống nhất này goị là đối tươṇ g. Trong môṭ đối tươṇ g, dữ liêụ hay thao tác hay cả hai có thể là riêng (private) hoăc̣ chung (public) của đối tươṇ g đó. Thao tác hay dữ liêụ riêng là thuôc̣ về đối tươṇ g đó chỉ đươc̣ truy câp̣ bởi các thành phâǹ của đối tươṇ g, điều này nghiã là thao tác hay dữ liêụ riêng không thể truy câp̣ bởi các phâǹ khác của chương triǹ h tồn taị ngoài đối tươṇ g. Khi thao tác hay dữ liêụ là chung,
  21. các phâǹ khác của chương triǹ h có thể truy câp̣ nó măc̣ dù nó đươc̣ điṇ h nghiã trong môṭ đối tươṇ g. Các thành phâǹ chung của môṭ đối tươṇ g dùng để cung cấp môṭ giao diêṇ có điều khiển cho các thành thành riêng của đối tươṇ g.Cơ chế đóng gói là phương thứ c tốt để thưc̣ hiêṇ cơ chế che dấu thông tin so với các ngôn ngữ lâp̣ triǹ h cấu trúc. 1.2.2 Tính kế thừa (Inheritance) Chúng ta có thể xây dưṇ g các lớp mới từ các lớp cũ thông qua sư ̣ kế thừa. Môṭ lớp mới còn goị là lớ p dâñ xuất (derived class), có thể thừa hưởng dữ liêụ và các phương thứ c của lớ p cơ sở (base class) ban đâù . Trong lớp này, có thể bổ sung các thành phâǹ dữ liêụ và các phương thứ c mới vào những thành phâǹ dữ liêụ và các phương thứ c mà nó thừa hưởng từ lớp cơ
  22. sở. Mỗi lớp (kể cả lớp dâñ xuất) có thể có môṭ số lươṇ g bất kỳ các lớp dâñ xuất. Qua cơ cấu kế thừa này, daṇ g hiǹ h cây của các lớp đươc̣ hiǹ h thành. Daṇ g cây của các lớp trông giống như các cây gia phả vi ̀ thế các lớp cơ sở còn đươc̣ goị là lớ p cha (parent class) và các lớp dâñ xuất đươc̣ goị là lớ p con (child class) . du ̣ 1.2: Chúng ta sẽ xây dưṇ g môṭ tâp̣ các lớp mô tả cho thư viêṇ các ấn phẩm. Có hai kiểu ấn phẩm: tap̣ chí và sách. Chúng ta có thể taọ môṭ ấn phẩm tổng quát băǹ g cách điṇ h nghiã các thành phâǹ dữ liêụ tương ứ ng với số trang, ma ̃ số tra cứ u, ngày tháng xuất bản, bản quyền và nhà xuất bản. Các ấn phẩm có thể đươc̣ lấy ra, cất đi và đoc̣ . Đó là các phương thứ c thưc̣
  23. hiêṇ trên môṭ ấn phẩm. Tiếp đó chúng ta điṇ h nghiã hai lớp dâñ xuất tên là tap̣ chí và sách. Tap̣ chí có tên, số ký phát hành và chứ a nhiều bài của các tác giả khác nhau . Các thành phâǹ dữ liêụ tương ứ ng với các yếu tố này đươc̣ đăṭ vào điṇ h nghiã của lớp tap̣ chí. Tap̣ chí cũng câǹ có môṭ phương thứ c nữa đó là đăṭ mua. Các thành phâǹ dữ liêụ xác điṇ h cho sách sẽ bao gồm tên của (các) tác giả, loaị bià (cứ ng hay mềm) và số hiêụ ISBN của nó. Như vâỵ chúng ta có thể thấy, sách và tap̣ chí có chung các đăc̣ trưng ấn phẩm, trong khi vâñ có các thuôc̣ tính riêng của chúng.
  24. Hiǹ h 1.1: Lớp ấn phẩm và các lớp dâñ xuất của nó. Với tính kế thừa, chúng ta không phải
  25. mất công xây dưṇ g laị từ đâù các lớp mới, chỉ câǹ bổ sung để có đươc̣ trong các lớp dâñ xuất các đăc̣ trưng câǹ thiết. 1.2.3 Tính đa hiǹ h (Polymorphism) Đó là khả năng để cho môṭ thông điêp̣ có thể thay đổi cách thưc̣ hiêṇ của nó theo lớp cu ̣ thể của đối tươṇ g nhâṇ thông điêp̣ . Khi môṭ lớp dâñ xuất đươc̣ taọ ra, nó có thể thay đổi cách thưc̣ hiêṇ các phương thứ c nào đó mà nó thừa hưởng từ lớp cơ sở của nó. Môṭ thông điêp̣ khi đươc̣ gởi đến môṭ đối tươṇ g của lớp cơ sở, sẽ dùng phương thứ c đa ̃ điṇ h nghiã cho nó trong lớp cơ sở. Nếu môṭ lớp dâñ xuất điṇ h nghiã laị môṭ phương thứ c thừa hưởng từ lớp cơ sở của nó thi ̀ môṭ thông điêp̣ có cùng tên với phương thứ c này, khi đươc̣ gởi tới môṭ đối tươṇ g của lớp dâñ xuất sẽ goị phương thứ c đa ̃ điṇ h nghiã cho lớp dâñ
  26. xuất. Ví du ̣ 1.3: Xét laị ví du ̣ 1.2, chúng ta thấy răǹ g cả tap̣ chí và và sách đều phải có khả năng lấy ra. Tuy nhiên phương pháp lấy ra cho tap̣ chí có khác so với phương pháp lấy ra cho sách, măc̣ dù kết quả cuối cùng giống nhau. Khi phải lấy ra tap̣ chí, thì phải sử duṇ g phương pháp lấy ra riêng cho tap̣ chí (dưạ trên môṭ bản tra cứ u) nhưng khi lấy ra sách thi ̀ laị phải sử duṇ g phương pháp lấy ra riêng cho sách (dưạ trên hệ thống phiếu lưu trữ). Tính đa hiǹ h cho phép chúng ta xác điṇ h môṭ phương thứ c để lấy ra môṭ tap̣ chí hay môṭ cuốn sách. Khi lấy ra môṭ tap̣ chí nó sẽ dùng phương thứ c lấy ra dành riêng cho tap̣ chí, còn khi lấy ra môṭ cuốn sách thi ̀ nó sử duṇ g phương thứ c lấy ra tương ứ ng với sách. Kết quả là chỉ câǹ môṭ tên phương thứ c
  27. duy nhất đươc̣ dùng cho cả hai công viêc̣ tiến hành trên hai lớp dâñ xuất có liên quan, măc̣ dù viêc̣ thưc̣ hiêṇ của phương thứ c đó thay đổi tùy theo từng lớp. Tính đa hiǹ h dưạ trên sư ̣ nối kết (Binding), đó là quá triǹ h gắn môṭ phương thứ c với môṭ hàm thưc̣ sư.̣ Khi các phương thứ c kiểu đa hiǹ h đươc̣ sử duṇ g thi ̀ triǹ h biên dic̣ h chưa thể xác điṇ h hàm nào tương ứ ng với phương thứ c nào sẽ đươc̣ goị . Hàm cu ̣ thể đươc̣ goị sẽ tuỳ thuôc̣ vào viêc̣ phâǹ tử nhâṇ thông điêp̣ lúc đó là thuôc̣ lớp nào, do đó hàm đươc̣ goị chỉ xác điṇ h đươc̣ vào lúc chương triǹ h chaỵ . Điều này goị là sư ̣ kết nối muôṇ (Late binding) hay kết nối lúc chaỵ (Runtime binding) vi ̀ nó xảy ra khi chương triǹ h đang thưc̣ hiêṇ .
  28. Hiǹ h 1.2: Minh hoạ tính đa hiǹ h đối với lớp ấn phẩm và các lớp dâñ xuất của nó. 1.3 CÁ C NGÔN NGỮ VÀ VÀ I Ứ NG DUṆ G CỦ A OOP Xuất phát từ tư tưởng của ngôn ngữ SIMULA67, trung tâm nghiên cứ u Palo Alto (PARC) của hañ g XEROR đa ̃ tâp̣ trung 10 năm nghiên cứ u để hoàn thiêṇ ngôn ngữ OOP đâù tiên với tên goị là Smalltalk. Sau đó các ngôn ngữ OOP lâǹ lươṭ ra đời như Eiffel, Clos, Loops, Flavors, Object Pascal, Object C, C++, Delphi, Java Chính XEROR trên cơ sở ngôn ngữ OOP đa ̃ đề ra tư tưởng giao diêṇ biểu tươṇ g trên màn hiǹ h (icon base screen interface), kể từ đó Apple Macintosh cũng như Microsoft
  29. Windows phát triển giao diêṇ đồ hoạ như ngày nay. Trong Microsoft Windows, tư tưởng OOP đươc̣ thể hiêṇ môṭ cách rõ nét nhất đó là "chúng ta click vào đối tươṇ g", mỗi đối tươṇ g có thể là control menu, control menu box, menu bar, scroll bar, button, minimize box, maximize box, sẽ đáp ứ ng công viêc̣ tùy theo đăc̣ tính của đối tươṇ g. Turbo Vision của hañ g Borland là môṭ ứ ng duṇ g OOP tuyêṭ vời, giúp lâp̣ triǹ h viên không quan tâm đến chi tiết của chương triǹ h gia diêṇ mà chỉ câǹ thưc̣ hiêṇ các nôị dung chính của vấn đề. 2.1 LIC̣ H SỬ CỦ A C++ Vào những năm đâù thâp̣ niên 1980, người dùng biết C++ với tên goị "C with Classes" đươc̣ mô tả trong hai bài báo của Bjarne Stroustrup (thuôc̣ AT&T Bell Laboratories) với nhan đề "Classes: An
  30. Abstract Data Type Facility for the C Language" và "Adding Classes to C : AnExercise in Language Evolution". Trong công triǹ h này, tác giả đa ̃ đề xuất khái niêṃ lớp, bổ sung viêc̣ kiểm tra kiểu tham số của hàm, các chuyển đổi kiểu và môṭ số mở rôṇ g khác vào ngôn ngữ C. Bjarne Stroustrup nghiên cứ u mở rôṇ g ngôn ngữ C nhăm̀ đaṭ đến môṭ ngôn ngữ mô phỏng (simulation language) với những tính năng hướng đối tươṇ g. Trong năm 1983, 1984, ngôn ngữ "C with Classes" đươc̣ thiết kế laị, mở rôṇ g hơn rồi môṭ triǹ h biên dic̣ h ra đời. Và chính từ đó, xuất hiêṇ tên goị "C++". Bjarne Stroustrup mô tả ngôn ngữ C++ lâǹ đâù tiên trong bài báo có nhan đề "Data Abstraction in C". Sau môṭ vài hiêụ chỉnh C++ đươc̣ công bố rôṇ g raĩ trong quyển
  31. "The C++ Programming Language" của Bjarne Stroustrup xuất hiêṇ đánh dấu sự hiêṇ diêṇ thưc̣ sư ̣ của C++, người lâp̣ tiǹ h chuyên nghiêp̣ từ đây đa ̃ có môṭ ngôn ngữ đủ maṇ h cho các dữ án thưc̣ tiễn của miǹ h. Về thưc̣ chất C++ giống như C nhưng bổ sung thêm môṭ số mở rôṇ g quan troṇ g, đăc̣ biêṭ là ý tưởng về đối tươṇ g, lâp̣ triǹ h điṇ h hướng đối tươṇ g.Thâṭ ra các ý tưởng về cấu trúc trong C++ đa ̃ xuất phát vào các năm 1970 từ Simula 70 và Algol 68. Các ngôn ngữ này đa ̃ đưa ra các khái niêṃ về lớp và đơn thể. Ada là môṭ ngôn ngữ phát triển từ đó, nhưng C++ đa ̃ khẳng điṇ h vai trò thưc̣ sư ̣ của miǹ h. 2.2 CÁ C MỞ RỘNG CỦ A C++ 2.2.1 Các từ khóa mới của C++
  32. Để bổ sung các tính năng mới vào C, môṭ số từ khóa (keyword) mới đa ̃ đươc̣ đưa vào C++ ngoài các từ khóa có trong C. Các chương triǹ h băǹ g C nào sử duṇ g các tên trùng với các từ khóa câǹ phải thay đổi trước khi chương triǹ h đươc̣ dic̣ h laị băǹ g C++. Các từ khóa mới này là : asm catch class delete new operator private protected this throw try virtual 2.2.2 Cách ghi chú thích C++ chấp nhâṇ hai kiểu chú thích. Các lâp̣ triǹ h viên băǹ g C đa ̃ quen với cách chú thích băǹ g /* */. Triǹ h biên dic̣ h sẽ bỏ qua moị thứ năm̀ giữa /* */. Ví du ̣2.1: Trong chương triǹ h sau :
  33. CT2_1.CPP 1: /* 2: Chương triǹ h in các số từ 0 đến 9. 3: */ 4: #include 5: int main() 6: { 7: int I; 8: for(I = 0; I < 10 ; ++ I)// 0 - 9 9: cout<<I<<"\n"; // In ra 0 - 9 10: return 0; 11: } Moị thứ năm̀ giữa /* */ từ dòng 1 đến dòng 3 đều đươc̣ chương triǹ h bỏ qua.
  34. Chương triǹ h này còn minh hoạ cách chú thích thứ hai. Đó là cách chú thích bắt đâù băǹ g // ở dòng 8 và dòng 9. Chúng ta chaỵ ví du ̣2.1, kết quả ở hiǹ h 2.1. Hiǹ h 2.1: Kết quả của ví du ̣2.1 Nói chung, kiểu chú thích /* */ đươc̣ dùng cho các khối chú thích lớn gồm nhiều dòng, còn kiểu // đươc̣ dùng cho các chú thích môṭ dòng.
  35. 2.2.3 Dòng nhâp̣ /xuất chuẩn Trong chương triǹ h C, chúng ta thường sử duṇ g các hàm nhâp̣ /xuất dữ liêụ là printf() và scanf(). Trong C++ chúng ta có thể dùng dòng nhâp̣ /xuất chuẩn (standard input/output stream) để nhâp̣ /xuất dữ liêụ thông qua hai biến đối tươṇ g của dòng (stream object) là c o u t và cin. Ví du ̣ 2.2: Chương triǹ h nhâp̣ vào hai số. Tính tổng và hiêụ của hai số vừa nhâp̣ .
  36. CT2_2.CPP 1: #include 2: int main() 3: { 4: int X, Y; 5: cout >X; 7: cout >Y; 9: cout<<"Tong cua chung:"<<X+Y<<"\n"; 10: cout<<"Hieu cua chung:"<<X-Y<<"\n"; 11: return 0; 12: } Để thưc̣ hiêṇ dòng xuất chúng ta sử
  37. duṇ g biến cout (console output) kết hơp̣ với toán tử chèn (insertion operator) > như ở các dòng 6 và 8. Khi sử duṇ g cout hay cin, chúng ta phải kéo file iostream.h như dòng 1. Chúng ta sẽ tim̀ hiểu kỹ về dòng nhâp̣ /xuất ở chương 8. Chúng ta chaỵ ví du ̣ 2.2 , kết quả ở hiǹ h 2.2. Hiǹ h 2.2: Kết quả của ví du ̣2.2
  38. Hiǹ h 2.3: Dòng nhâp̣ /xuất dữ liêụ 2.2.4 Cách chuyển đổi kiểu dữ liêụ Hiǹ h thứ c chuyển đổi kiểu trong C tương đối tối nghiã , vi ̀ vâỵ C++ trang bi ̣ thêm môṭ cách chuyển đổi kiểu giống như
  39. môṭ lêṇ h goị hàm. Ví du ̣2.3: CT2_3.CPP 1: #include 2: int main() 3: { 4: int X = 200; 5: long Y = (long) X; //Chuyển đổi kiểu theo cách của C 6: long Z = long(X); // Chuyển đ ổi kiểu theo cách mới của C++ 7: cout<< "X = "<<X<<"\n"; 8: cout<< "Y = "<<Y<<"\n"; 9: cout<< "Z = "<<Z<<"\n"; 10: return 0;
  40. 11: } Chúng ta chaỵ ví du ̣ 2.3 , kết quả ở hiǹ h 2.4. Hiǹ h 2.4: Kết quả của ví du ̣2.3 2.2.5 Vi ̣trí khai báo biến Trong chương triǹ h C đòi hỏi tất cả các khai báo bên trong môṭ phaṃ vi cho trước phải đươc̣ đăṭ ở ngay đâù của phaṃ vi đó. Điều này có nghiã là tất cả các khai báo toàn cuc̣ phải đăṭ trước tất cả các hàm và các khai báo cuc̣ bô ̣ phải đươc̣ tiến hành trước tất cả các lêṇ h thưc̣ hiêṇ . Ngươc̣ laị C++ cho phép chúng ta khai
  41. báo linh hoaṭ bất kỳ vi ̣ trí nào trong môṭ phaṃ vi cho trước (không nhất thiết phải ngay đâù của phaṃ vi), chúng ta xen kẽ viêc̣ khai báo dữ liêụ với các câu lêṇ h thưc̣ hiêṇ . Ví du ̣ 2.4: Chương triǹ h mô phỏng môṭ máy tính đơn giản CT2_4.CPP 1: #include 2: int main() 3: { 4: int X; 5: cout >X; 7: int Y; 8: cout >Y;
  42. 10: char Op; 11: cout >Op; 13: switch(Op) 14: { 15: case ‘+’: 16: cout<<"Ket qua:"<<X+Y<<"\n"; 17: break; 18: case ‘-’: 19: cout<<"Ket qua:"<<X-Y<<"\n"; 20: break; 21: case ‘*’: 22: cout<<"Ket qua:"<<long(X)*Y<<"\n"; 23: break; 24: case ‘/’: 25: if (Y) 26: cout<<"Ket qua:"<<float(X)/Y<<"\n"; 27: else 28: cout<<"Khong the chia duoc!" <<"\n"; 9; 9;
  43. 29: break; 30: default : 31: cout<<"Khong hieu toan tu nay!"<<"\n"; 32: } 33: return 0; 34: } Trong chương triǹ h chúng ta xen kẻ khai báo biến với lêṇ h thưc̣ hiêṇ ở dòng 4 đến dòng 12. Chúng ta chaỵ ví du ̣ 2.4, kết quả ở hiǹ h 2.5. Hiǹ h 2.5: Kết quả của ví du ̣2.4 Khi khai báo môṭ biến trong chương
  44. triǹ h, biến đó sẽ có hiêụ lưc̣ trong phaṃ vi của chương triǹ h đó kể từ vi ̣ trí nó xuất hiêṇ . Vi ̀ vâỵ chúng ta không thể sử duṇ g môṭ biến đươc̣ khai báo bên dưới nó. 2.2.6 Các biến const Trong ANSI C, muốn điṇ h nghiã môṭ hăǹ g có kiểu nhất điṇ h thi ̀ chúng ta dùng biến const (vi ̀ nếu dùng #define thi ̀ taọ ra các hăǹ g không có chứ a thông tin về kiểu). Trong C++, các biến const linh hoaṭ hơn môṭ cách đáng kể: C++ xem const cũng như #define nếu như chúng ta muốn dùng hăǹ g có tên trong chương triǹ h. Chính vi ̀ vâỵ chúng ta có thể dùng const để quy điṇ h kích thước của môṭ mảng như đoaṇ mã s a u :
  45. int ArraySize = 100; int X[ArraySize]; khai báo môṭ biến const trong C++ thì chúng ta phải khởi taọ môṭ giá tri ̣ban đâù nhưng đối với ANSI C thi ̀ không nhất thiết phải làm như vâỵ (vi ̀ triǹ h biên dic̣ h ANSI C tư ̣ đôṇ g gán tri ̣zero cho biến const nếu chúng ta không khởi taọ giá tri ̣ban đâù cho nó). Phaṃ vi của các biến const giữa ANSI C và C++ khác nhau. Trong ANSI C, các biến const đươc̣ khai báo ở bên ngoài moị hàm thi ̀ chúng có phaṃ vi toàn cuc̣ , điều này nghiã là chúng có thể nhiǹ thấy cả ở bên ngoài file mà chúng đươc̣ điṇ h nghiã , trừ khi chúng đươc̣ khai báo là static.
  46. Nhưng trong C++, các biến const đươc̣ hiểu măc̣ điṇ h là static. 2.2.7 Về struct, union và enum Trong C++, các struct và union thưc̣ sư ̣ các các kiểu class. Tuy nhiên có sự thay đổi đối với C++. Đó là tên của struct và union đươc̣ xem luôn là tên kiểu giống như khai báo băǹ g lêṇ h typedef vâỵ . Trong C, chúng ta có thể có đoaṇ ma ̃ sau : struct Complex { float Real; float Imaginary; }; struct Complex C; Trong C++, vấn đề trở nên đơn giản hơn: struct Complex
  47. { float Real; float Imaginary; }; Complex C; Quy điṇ h này cũng áp duṇ g cho cả union và enum. Tuy nhiên để tương thích với C, C++ vâñ chấp nhâṇ cú pháp cũ. Môṭ kiểu union đăc̣ biêṭ đươc̣ thêm vào C++ goị là union năc̣ danh (anonymous union). Nó chỉ khai báo môṭ loaṭ các trường(field) dùng chung môṭ vùng điạ chỉ bô ̣ nhớ. Môṭ union năc̣ danh không có tên tag, các trường có thể đươc̣ truy xuất trưc̣ tiếp băǹ g tên của chúng. Chẳng haṇ như đoaṇ ma ̃ sau: union
  48. { int Num; float Value; }; Cả hai Num và Value đều dùng chung môṭ vi ̣trí và không gian bô ̣ nhớ. Tuy nhiên không giống như kiểu union có tên, các trường của union năc̣ danh thi ̀ đươc̣ truy xuất trưc̣ tiếp, chẳng haṇ như sau: Num = 12; Value = 30.56; 2.2.8 Toán tử điṇ h phaṃ vi Toán tử điṇ h phaṃ vi (scope resolution operator) ký hiêụ là ::, nó đươc̣ dùng truy xuất môṭ phâǹ tử bi ̣ che bởi phaṃ vi hiêṇ thời. Ví du ̣2.5 : CT2_5.CPP
  49. 1: #include 2: int X = 5; 3: int main() 4: { 5: int X = 16; 6: cout<< "Bien X ben trong = "<<X<<"\n"; 7: cout<< "Bien X ben ngoai = "<<::X<<"\n"; 8: return 0; 9: } Chúng ta chaỵ ví du ̣2.5, kết quả ở hiǹ h 2.6 Hiǹ h 2.6: Kết quả của ví du ̣2.5 Toán tử điṇ h phaṃ vi còn đươc̣ dùng
  50. trong các điṇ h nghiã hàm của các phương thứ c trong các lớp, để khai báo lớp chủ của các phương thứ c đang đươc̣ điṇ h nghiã đó. Toán tử điṇ h phaṃ vi còn có thể đươc̣ dùng để phân biêṭ các thành phâǹ trùng tên của các lớp cơ sở khác nhau. 2.2.9 Toán tử new và delete Trong các chương triǹ h C, tất cả các cấp phát đôṇ g bô ̣ nhớ đều đươc̣ xử lý thông qua các hàm thư viêṇ như malloc(), calloc() và free(). C++ điṇ h nghiã môṭ phương thứ c mới để thưc̣ hiêṇ viêc̣ cấp phát đôṇ g bô ̣ nhớ băǹ g cách dùng hai toán tử new và delete. Sử duṇ g hai toán tử này sẽ linh hoaṭ hơn rất nhiều so với các hàm thư viêṇ của C. Đoaṇ chương triǹ h sau dùng để cấp phát vùng nhớ đôṇ g theo lối cổ điển của C. int *P;
  51. P = malloc(sizeof(int)); if (P==NULL) printf("Khong con du bo nho de cap phat\n"); else { *P = 290; printf("%d\n", *P); free(P); } Trong C++, chúng ta có thể viết laị đoaṇ chương triǹ h trên như sau: int *P; P = new int; if (P==NULL) cout<<"Khong con du bo nho de cap phat\n"; else {
  52. *P = 290; cout<<*P<<"\n"; delete P; } Chúng ta nhâṇ thấy răǹ g, cách viết của C++ sáng sủa và dễ sử duṇ g hơn nhiều. Toán tử new thay thế cho hàm malloc() hay calloc() của C có cú pháp như sau : new type_name new ( type_name ) new type_name initializer new ( type_name ) initializer Trong đó : type_name: Mô tả kiểu dữ liêụ đươc̣ cấp phát. Nếu kiểu dữ liêụ mô tả phứ c tap̣ , nó có thể đươc̣ đăṭ bên trong các dấu ngoăc̣ . initializer: Giá tri ̣khởi đôṇ g của vùng nhớ đươc̣ cấp phát.
  53. Nếu toán tử new cấp phát không thành công thi ̀ nó sẽ trả về giá tri ̣NULL. Còn toán tử delete thay thế hàm free() của C, nó có cú pháp như sau : delete pointer delete [] pointer Chúng ta có thể vừa cấp phát vừa khởi đôṇ g như sau : int *P; P = new int(100); if (P!=NULL) { cout<<*P<<"\n"; delete P; } else cout<<"Khong con du bo nho de cap phat\n"; Để cấp phát môṭ mảng, chúng ta làm
  54. như sau : int *P; P = new int[10]; //Cấp phát mảng 10 số nguyên if (P!=NULL) { for(int I = 0;I<10;++) P[I]= I; for(I = 0;I<10;++) cout<<P[I]<<"\n"; delete []P; } else cout<<"Khong con du bo nho de cap phat\n"; Chú ý: Đối với viêc̣ cấp phát mảng chúng ta không thể vừa cấp phát vừa khởi đôṇ g giá tri ̣ cho chúng, chẳng haṇ đoaṇ chương triǹ h sau là sai :
  55. int *P; P = new (int[10])(3); //Sai !!! Ví du ̣ 2.6: Chương triǹ h taọ môṭ mảng đôṇ g, khởi đôṇ g mảng này với các giá tri ̣ ngâũ nhiên và sắp xếp chúng. CT2_6.CPP 1: #include 2: #include 3: #include 4: int main() 5: { 6: int N; 7: cout >N; 9: int *P=new int[N]; 10: if (P==NULL) 11: { 12: cout<<"Khong con bo nho de cap phat\n";
  56. 13: return 1; 14: } 15: srand((unsigned)time(NULL)); 16: for(int I=0;I P[J]) 24: { 25: int Temp=P[I]; 26: P[I]=P[J]; 27: P[J]=Temp; 28: } 29: cout<<"\nMang sau khi sap xep\n"; 30: for(I=0;I<N;++I)
  57. 31: cout<<P[I]<<" "; 32: delete []P; 33: return 0; 34: } Chúng ta chaỵ ví du ̣2.6, kết quả ở hiǹ h 2.7 Hiǹ h 2.7: Kết quả của ví du ̣2.6 Ví du ̣ 2.7: Chương triǹ h côṇ g hai ma trâṇ trong đó mỗi ma trâṇ đươc̣ cấp phát đôṇ g.Chúng ta có thể xem mảng hai chiều như mảng môṭ chiều như hiǹ h 2.8
  58. Hiǹ h 2.8: Mảng hai chiều có thể xem như mảng môṭ chiều. Goị X là mảng hai chiều có kích thước m dòng và n côṭ .A là mảng môṭ chiều tương ứ ng.Nếu X[i][j] chính là A[k] thi ̀ k = i*n + j Chúng ta
  59. có chương triǹ h như sau : CT2_7.CPP 1: #include 2: #include 3: //prototype 4: void AddMatrix(int * A,int *B,int*C,int M,int N); 5: int AllocMatrix(int A,int M,int N); 6: void FreeMatrix(int *A); 7: void InputMatrix(int *A,int M,int N,char Symbol); 8: void DisplayMatrix(int *A,int M,int N); 9: 10: int main() 11: { 12: int M,N; 13: int *A = NULL,*B = NULL,*C = NULL; 14: 15: clrscr(); 16: cout<<"Nhap so dong cua ma tran:";
  60. 17: cin>>M; 18: cout >N; 20: //Cấp phát vùng nhớ cho ma trâṇ A 21: if (!AllocMatrix(&A,M,N)) 22: { //endl: Xuất ra kí tư ̣ xuống dòng (‘\n’) 23: cout<<"Khong con du bo nho!"<<endl; 24: return 1; 25: } 26: //Cấp phát vùng nhớ cho ma trâṇ B 27: if (!AllocMatrix(&B,M,N)) 28: { 29: cout<<"Khong con du bo nho!"<<endl; 30: FreeMatrix(A);//Giải phóng vùng nhớ A 31: return 1; 32: } 33: //Cấp phát vùng nhớ cho ma trâṇ C 34: if (!AllocMatrix(&C,M,N)) 35: {
  61. 36: cout<<"Khong con du bo nho!"<<endl; 37: FreeMatrix(A);//Giải phóng vùng nhớ A 38: FreeMatrix(B);//Giải phóng vùng nhớ B 39: return 1; 40: } 41: cout<<"Nhap ma tran thu 1"<<endl; 42: InputMatrix(A,M,N,'A'); 43: cout<<"Nhap ma tran thu 2"<<endl; 44: InputMatrix(B,M,N,'B'); 45: clrscr(); 46: cout<<"Ma tran thu 1"<<endl; 47: DisplayMatrix(A,M,N); 48: cout<<"Ma tran thu 2"<<endl; 49: DisplayMatrix(B,M,N); 50: AddMatrix(A,B,C,M,N); 51: cout<<"Tong hai ma tran"<<endl; 52: DisplayMatrix(C,M,N); 53: FreeMatrix(A);//Giải phóng vùng nhớ A 54: FreeMatrix(B);//Giải phóng vùng nhớ B
  62. 55: FreeMatrix(C);//Giải phóng vùng nhớ C 56: return 0; 57: } 68: //Côṇ g hai ma trâṇ 69: void AddMatrix(int *A,int *B,int*C,int M,int N) 70: { 71: for(int I=0;I<M*N;++I) 72: C[I] = A[I] + B[I]; 73: } 74: //Cấp phát vùng nhớ cho ma trâṇ 75: int AllocMatrix(int A,int M,int N) 76: { 77: *A = new int [M*N]; 78: if (*A == NULL) 79: return 0; 80: return 1; 81: } 82: //Giải phóng vùng nhớ 83: void FreeMatrix(int *A)
  63. 84: { 85: if (A!=NULL) 86: delete [] A; 87: } 88: //Nhâp̣ các giá tri ̣của ma trâṇ 89: void InputMatrix(int *A,int M,int N,char Symbol) 90: { 91: for(int I=0;I >A[I*N+J]; 96: } 97: } 100: //Hiển thi ̣ma trâṇ 101: void DisplayMatrix(int *A,int M,int N) 102: { 103: for(int I=0;I<M;++I) 104: {
  64. 105: for(int J=0;J<N;++J) 106: { 107: out.width(7);//Hien thi canh le phai voi chieu dai 7 ky tu 108: cout<<A[I*N+J]; 109: } 110: cout<<endl; 111: } 112: } Chúng ta chaỵ ví du 2.7 , kết quả ở hiǹ h 2.9
  65. Hiǹ h 2.9: Kết quả của ví du ̣2.7 Môṭ cách khác để cấp phát mảng hai chiều A gồm M dòng và N côṭ như sau:
  66. int A = new int *[M]; int * Tmp = new int[M*N]; for(int I=0;I<M;++I) { A[I]=Tmp; Tmp+=N; } //Thao tác trên mảng hai chiều A delete [] *A; delete [] A; Toán tử ne w còn có môṭ thuâṇ lơị khác, đó là tất cả các lỗi cấp phát đôṇ g đều có thể bắt đươc̣ băǹ g môṭ hàm xử lý lỗi do người dùng tư ̣ điṇ h nghiã . C++ có điṇ h nghiã môṭ con trỏ (pointer) trỏ đến hàm đăc̣ biêṭ. Khi toán tử new đươc̣ sử duṇ g để cấp phát đôṇ g và môṭ lỗi xảy ra do cấp phát, C++ tư ̣ goị đến hàm đươc̣ chỉ
  67. bởi con trỏ này. Điṇ h nghiã của con trỏ này như sau: void (*pvf) (); pvf _new_handler(pvf p); này có nghiã là con trỏ _new_handler là con trỏ trỏ đến hàm không có tham số và không trả về giá tri.̣ Sau khi chúng ta điṇ h nghiã hàm như vâỵ và gán điạ chỉ của nó c ho _new_handler chúng ta có thể bắt đươc̣ tất cả các lỗi do cấp phát đôṇ g. Ví du ̣2.8:
  68. CT2_8.CPP 1: #include 2: #include 3: #include 4: 5: void MyHandler(); 6: 7: unsigned long I = 0; 9; 8: void main() 9: { 10: int *A; 11: _new_handler = MyHandler; 12: for( ; ; ++I) 13: A = new int; 14: 15: } 16:
  69. 17: void MyHandler() 18: { 19: cout<<"Lan cap phat thu "<<I<<endl; 20: cout<<"Khong con du bo nho!"<<endl; 21: exit(1); 22: } Sử duṇ g con trỏ _new_handler chúng ta phải include file new.h như ở dòng 3. Chúng ta chaỵ ví du ̣ 2.8, kết quả ở hiǹ h 2.10. Hiǹ h 2.10: Kết quả của ví du ̣2.8 Thư viêṇ cũng còn có môṭ hàm đươc̣ điṇ h nghiã trong new.h là hàm có
  70. prototype sau : void ( * set_new_handler(void (* my_handler)() ))(); Hàm set_new_handler() dùng để gán môṭ hàm cho _new_handler. Ví du ̣2.9: CT2_9.CPP 1: #include 2: #include 3: #include 4: 5: void MyHandler(); 6: 7: int main(void) 8: {
  71. 9: 10: char *Ptr; 11: 12: set_new_handler(MyHandler); 13: Ptr = new char[64000u]; 14: set_new_handler(0); //Thiết lâp̣ laị giá tri ̣măc̣ điṇ h 15: return 0; 16: } 17: 18: void MyHandler() 19: { 20: cout <<endl<<"Khong con du bo nho"; 21: exit(1); 22 } Chúng ta chaỵ ví du ̣2.9, kết quả ở hiǹ h 2.11
  72. Hiǹ h 2.11: Kết quả của ví du ̣2.9 2.2.10 Hàm inline Môṭ chương triǹ h có cấu trúc tốt sử duṇ g các hàm để chia chương triǹ h thành các đơn vi ̣ đôc̣ lâp̣ có logic riêng. Tuy nhiên, các hàm thường phải chứ a môṭ loaṭ các xử lý điểm vào (entry point): tham số phải đươc̣ đẩy vào stack, môṭ lêṇ h goị phải đươc̣ thưc̣ hiêṇ và sau đó viêc̣ quay trở về cũng phải đươc̣ thưc̣ hiêṇ băǹ g cách giải phóng các tham số ra khỏi stack. Khi các xử lý điểm vào châṃ chap̣ thường các lâp̣ triǹ h viên C phải sử duṇ g cách chép lâp̣ laị các đoaṇ chương triǹ h nếu muốn tăng hiêụ quả. Để tránh khỏi phải xử lý điểm vào, C++
  73. trang bi ̣ thêm từ khóa inline để loaị viêc̣ goị hàm. Khi đó triǹ h biên dic̣ h sẽ không biên dic̣ h hàm này như môṭ đoaṇ chương triǹ h riêng biêṭ mà nó sẽ đươc̣ chèn thẳng vào các chỗ mà hàm này đươc̣ goị . Điều này làm giảm viêc̣ xử lý điểm vào mà vâñ cho phép môṭ chương triǹ h đươc̣ tổ chứ c dưới daṇ g có cấu trúc. Cú pháp của hàm i n l i n e như sau : inline data_type function_name ( parameters ) { } Trong đó: data_type: Kiểu trả về của hàm. Function_name:Tên của hàm. Parameters: Các tham số của hàm.
  74. Ví du ̣ 2.10: Tính thể tích của hiǹ h lâp̣ phương CT2_10.CPP 1: #include 2: inline float Cube(float S) 3: { 4: return S*S*S; 5: } 6: 7: int main() 8: { 9: cout<<"Nhap vao chieu dai canh cua hinh lap phuong:"; 10: float Side;
  75. 11: cin>>Side; 12: cout<<"The tich cua hinh lap phuong = " <<Cube(Side); 13: return 0; 14: } Chúng ta chaỵ ví du ̣ 2.10, kết quả ở hiǹ h 2.12 Hiǹ h 2.12: Kết quả của ví du ̣2.10 Chú ý: Sử duṇ g hàm inline sẽ làm cho chương triǹ h lớn lên vi ̀ triǹ h biên dic̣ h chèn đoaṇ chương triǹ h vào các chỗ mà hàm này đươc̣ goị . Do đó thường các hàm inline thường là các hàm nhỏ, ít phứ c
  76. t a ̣ p . Các hàm inline phải đươc̣ điṇ h nghiã trước khi sử duṇ g. Ở ví du ̣ 2.10 chúng ta sử a laị như sau thi ̀ chương triǹ h sẽ bi ̣báo lỗi: A.CPP 1: #include 2: float Cube(float S); 3: int main() 4: { 5: cout >Side;
  77. 8: cout<<"The tich cua hinh lap phuong = " <<Cube(Side); 9: return 0; 10: } 11: 12: inline float Cube(float S) 13: { 14: return S*S*S; 15: } Các hàm đê ̣ quy không đươc̣ là hàm inline. 2.2.11 Các giá tri ̣tham số măc̣ điṇ h Môṭ trong các đăc̣ tính nổi bâṭ nhất của C++ là khả năng điṇ h nghiã các giá tri ̣ tham số măc̣ điṇ h cho các hàm. Biǹ h thường khi goị môṭ hàm, chúng ta câǹ gởi
  78. môṭ giá tri ̣cho mỗi tham số đa ̃ đươc̣ điṇ h nghiã trong hàm đó, chẳng haṇ chúng ta có đoaṇ chương triǹ h sau: void MyDelay(long Loops); //prototype void MyDelay(long Loops) { for(int I = 0; I < Loops; ++I) ; } Mỗi khi hàm MyDelay() đươc̣ goị chúng ta phải gởi cho nó môṭ giá tri ̣ cho tham số Loops. Tuy nhiên, trong nhiều trường hơp̣ chúng ta có thể nhâṇ thấy răǹ g chúng ta luôn luôn goị hàm MyDelay() với cùng môṭ giá tri ̣Loops nào đó. Muốn vâỵ chúng ta sẽ dùng giá tri ̣măc̣ điṇ h cho tham số Loops, giả sử chúng ta muốn giá tri ̣măc̣
  79. điṇ h cho tham số Loops là 1000. Khi đó đoaṇ ma ̃ trên đươc̣ viết laị như sau : void MyDelay(long Loops = 1000); //prototype void MyDelay(long Loops) { for(int I = 0; I < Loops; ++I) ; } Mỗi khi goị hàm MyDelay() mà không gởi môṭ tham số tương ứ ng thi ̀ triǹ h biên dic̣ h sẽ tư ̣ đôṇ g gán cho tham số Loops giá tri ̣1000. MyDelay(); // Loops có giá tri ̣là 1000 MyDelay(5000); // Loops có giá tri ̣là 5000 Giá tri ̣măc̣ điṇ h cho tham số có thể là môṭ hăǹ g, môṭ hàm, môṭ biến hay môṭ biểu
  80. thứ c. Ví du ̣ 2.11: Tính thể tích của hiǹ h hôp̣ CT2_11.CPP 1: #include 2: int BoxVolume(int Length = 1, int Width = 1, int Height = 1); 3: 4: int main() 5: { 6: cout << "The tich hinh hop mac dinh: " 7: << BoxVolume() << endl << endl 8: << "The tich hinh hop voi chieu dai=10,do rong=1,chieu cao=1:" 9: << BoxVolume(10) << endl << endl
  81. 10: << "The tich hinh hop voi chieu dai=10,do rong=5,chieu cao=1:" 11: << BoxVolume(10, 5) << endl << endl 12: << "The tich hinh hop voi chieu dai=10,do rong=5,chieu cao=2:" 13: << BoxVolume(10, 5, 2)<< endl; 14: return 0; 15: } 16: //Tính thể tích của hiǹ h hôp̣ 17: int BoxVolume(int Length, int Width, int Height) 18: { 19: return Length * Width * Height; 20: } Chúng ta chaỵ ví du ̣ 2.11, kết quả ở hiǹ h 2.13
  82. Hiǹ h 2.13: Kết quả của ví du ̣2.11 Chú ý: Các tham số có giá tri ̣ măc̣ điṇ h chỉ đươc̣ cho trong prototype của hàm và không đươc̣ lăp̣ laị trong điṇ h nghiã hàm (Vi ̀ triǹ h biên dic̣ h sẽ dùng các thông tin trong prototype chứ không phải trong điṇ h nghiã hàm để taọ môṭ lêṇ h goị ). Môṭ hàm có thể có nhiều tham số có giá tri ̣ măc̣ điṇ h. Các tham số có giá tri ̣ măc̣ điṇ h câǹ phải đươc̣ nhóm laị vào các
  83. tham số cuối cùng (hoăc̣ duy nhất) của môṭ hàm. Khi goị hàm có nhiều tham số có giá tri ̣ măc̣ điṇ h, chúng ta chỉ có thể bỏ bớt các tham số theo thứ tư ̣ từ phải sang trái và phải bỏ liên tiếp nhau, chẳng haṇ chúng ta có đoaṇ chương triǹ h như sau: int MyFunc(int a= 1, int b , int c = 3, int d = 4); //prototype sai!!! int MyFunc(int a, int b = 2 , int c = 3, int d = 4); //prototype đúng MyFunc(); // Lỗi do tham số a không có giá tri ̣măc̣ điṇ h MyFunc(1);// OK, các tham số b, c và d lấy giá tri ̣măc̣ điṇ h MyFunc(5, 7); // OK, các tham số c và d lấy giá tri ̣măc̣ điṇ h MyFunc(5, 7, , 8); // Lỗi do các tham số bi ̣bỏ phải liên tiếp nhau
  84. 2.2.12 Phép tham chiếu Trong C, hàm nhâṇ tham số là con trỏ đòi hỏi chúng ta phải thâṇ troṇ g khi goị hàm. Chúng ta câǹ viết hàm hoán đổi giá tri ̣giữa hai số như sau: void Swap(int *X, int *Y); { int Temp = *X; *X = *Y; *Y = *Temp; } Để hoán đổi giá tri ̣hai biến A và B thì chúng ta goị hàm như sau: Swap(&A, &B); Rõ ràng cách viết này không đươc̣ thuâṇ tiêṇ lắm. Trong trường hơp̣ này, C++ đưa ra môṭ kiểu biến rất đăc̣ biêṭ goị là biến tham chiếu (reference variable). Môṭ biến tham chiếu giống như là môṭ bí danh
  85. của biến khác. Biến tham chiếu sẽ làm cho các hàm có thay đổi nôị dung các tham số của nó đươc̣ viết môṭ cách thanh thoát hơn. Khi đó hàm Swap() đươc̣ viết như sau: void Swap(int &X, int &Y); { int Temp = X; X = Y; Y = Temp ; } Chúng ta goị hàm như sau : Swap(A, B); Với cách goị hàm này, C++ tư ̣ gởi điạ chỉ của A v à B làm tham số cho hàm Swap(). Cách dùng biến tham chiếu cho tham số của C++ tương tư ̣ như các tham số đươc̣ khai báo là Var trong ngôn ngữ Pascal. Tham số này đươc̣ goị là tham số kiểu tham chiếu (reference parameter).
  86. Như vâỵ biến tham chiếu có cú pháp như sau : data_type & variable_name; Trong đó: data_type: Kiểu dữ liêụ của biến. variable_name: Tên của biến Khi dùng biến tham chiếu cho tham số chỉ có điạ chỉ của nó đươc̣ gởi đi chứ không phải là toàn bô ̣ cấu trúc hay đối tươṇ g đó như hiǹ h 2.14, điều này rất hữu duṇ g khi chúng ta gởi cấu trúc và đối tươṇ g lớn cho môṭ hàm.
  87. Hiǹ h 2.14: Môṭ tham số kiểu tham chiếu nhâṇ môṭ tham chiếu tới môṭ biến đươc̣ chuyển cho tham số của hàm. Ví du ̣ 2.12: Chương triǹ h hoán đổi giá tri ̣của hai biến. CT2_12.CPP 1: #include
  88. 2: //prototype 3 void Swap(int &X,int &Y); 4: 5: int main() 6: { 7: int X = 10, Y = 5; 8: cout<<"Truoc khi hoan doi: X = "<<X<<",Y = " <<Y<<endl; 9: Swap(X,Y); 10: cout<<"Sau khi hoan doi: X = "<<X<<",Y = " <<Y<<endl; 11: return 0; 12: } 13: 14: void Swap(int &X,int &Y) 15: { 16: int Temp=X; 17: X=Y; 18: Y=Temp;
  89. 19: } Chúng ta chaỵ ví du ̣ 2.12, kết quả ở hiǹ h 2.15 Hiǹ h 2.15: Kết quả của ví du ̣2.12 Đôi khi chúng ta muốn gởi môṭ tham số nào đó băǹ g biến tham chiếu cho hiêụ quả, măc̣ dù chúng ta không muốn giá tri ̣của nó bi ̣thay đổi thi ̀ chúng ta dùng thêm từ khóa const như sau : int MyFunc(const int & X); Hàm MyFunc() sẽ chấp nhâṇ môṭ tham số X gởi băǹ g tham chiếu nhưng const xác
  90. điṇ h răǹ g X không thể bi ̣ thay đổi.Biến tham chiếu có thể sử duṇ g như môṭ bí danh của biến khác (bí danh đơn giản như môṭ tên khác của biến gốc), chẳng haṇ như đoaṇ ma ̃ sau : int Count = 1; int & Ref = Count; //Taọ biến Ref như là môṭ bí danh của biến Count ++Ref; //Tăng biến Count lên 1 (sử duṇ g bí danh của biến Count) Các biến tham chiếu phải đươc̣ khởi đôṇ g trong phâǹ khai báo của chúng và chúng ta không thể gán laị môṭ bí danh của biến khác cho chúng. Chẳng haṇ đoaṇ ma ̃ sau là sai: int X = 1;
  91. int & Y; //Lỗi: Y phải đươc̣ khởi đôṇ g. Khi môṭ tham chiếu đươc̣ khai báo như môṭ bí danh của biến khác, moị thao tác thưc̣ hiêṇ trên bí danh chính là thưc̣ hiêṇ trên biến gốc của nó. Chúng ta có thể lấy điạ chỉ của biến tham chiếu và có thể so sánh các biến tham chiếu với nhau (phải tương thích về kiểu tham chiếu). Ví du ̣ 2.13: Moị thao tác trên trên bí danh chính là thao tác trên biến gốc của nó. CT2_13.CPP 1: #include 2: int main() 3: { 4: int X = 3; 5: int &Y = X; //Y la bí danh của X 6: int Z = 100;
  92. 7: 8: cout<<"X="<<X<<endl<<"Y="<<Y<<endl; 9: Y *= 3; 10: cout<<"X="<<X<<endl<<"Y="<<Y<<endl; 11: Y = Z; 12: cout<<"X="<<X<<endl<<"Y="<<Y<<endl; 13: return 0; 14: } Chúng ta chaỵ ví du ̣ 2.13, kết quả ở hiǹ h 2.16
  93. Hiǹ h 2.16: Kết quả của ví du ̣2.13 Ví du ̣ 2.14: Lấy điạ chỉ của biến tham chiếu CT2_14.CPP 1: #include 2: int main() 3: { 4: int X = 3; 5: int &Y = X; //Y la bí danh của X 6: 7: cout<<"Dia chi cua X = "<<&X<<endl; 8: cout<<"Dia chi cua bi danh Y= "<<&Y<<endl; 9: return 0; 10: }
  94. Chúng ta chaỵ ví du ̣ 2.14, kết quả ở hiǹ h 2.17 Hiǹ h 2.17: Kết quả của ví du ̣2.14 Chúng ta có thể taọ ra biến tham chiếu với viêc̣ khởi đôṇ g là môṭ hăǹ g, chẳng haṇ như đoaṇ ma ̃ sau :int & Ref = 45; trường hơp̣ này, triǹ h biên dic̣ h taọ ra môṭ biến taṃ thời chứ a tri ̣ hăǹ g và biến tham chiếu chính là bí danh của biến taṃ thời này. Điều này goị là tham chiếu đôc̣ lâp̣ (independent reference). Các hàm có thể trả về môṭ tham chiếu, nhưng điều này rất nguy hiểm. Khi hàm trả về môṭ tham chiếu tới môṭ biến cuc̣ bô ̣ của hàm thi ̀ biến này phải đươc̣ khai báo là static, ngươc̣ laị
  95. tham chiếu tới nó thi ̀ khi hàm kết thúc biến cuc̣ bô ̣ này sẽ bi ̣ bỏ qua. Chẳng haṇ như đoaṇ chương triǹ h sau: int & MyFunc() { static int X = 200; //Nếu không khai báo là static thi ̀ điều này rất nguy hiểm. return X; } Khi môṭ hàm trả về môṭ tham chiếu, chúng ta có thể goị hàm ở phái bên trái của môṭ phép gán. Ví du ̣2.15: CT2_15.CPP 1: #include 2: 3: int X = 4; 4: //prototype
  96. 5: int & MyFunc(); 6: 7: int main() 8: { 9: cout<<"X="<<X<<endl; 10: cout<<"X="<<MyFunc()<<endl; 11: MyFunc() = 20; //Nghiã là X = 20 12: cout<<"X="<<X<<endl; 13: return 0; 14: } 15: 16: int & MyFunc() 17: { 18: return X; 19: } Chúng ta chaỵ ví du ̣ 2.15, kết quả ở
  97. hiǹ h 2.18 Hiǹ h 2.18: Kết quả của ví du ̣2.15 Chú ý: Măc̣ dù biến tham chiếu trông giống như là biến con trỏ nhưng chúng không thể là biến con trỏ do đó chúng không thể đươc̣ dùng cấp phát đôṇ g. Chúng ta không thể khai báo môṭ biến tham chiếu chỉ đến biến tham chiếu hoăc̣ biến con trỏ chỉ đến biến tham chiếu. Tuy nhiên chúng ta có thể khai báo môṭ biến tham chiếu về biến con trỏ như đoaṇ mã sau: int X; int *P = &X;
  98. int * & Ref = P; 2 . 2 . 1 3 Phép đa năng hóa (Overloading) : Với ngôn ngữ C++, chúng ta có thể đa năng hóa các hàm và các toán tử (operator). Đa năng hóa là phương pháp cung cấp nhiều hơn môṭ điṇ h nghiã cho tên hàm đa ̃ cho trong cùng môṭ phaṃ vi. Triǹ h biên dic̣ h sẽ lưạ choṇ phiên bản thích hơp̣ của hàm hay toán tử dưạ trên các tham số mà nó đươc̣ goị . 2.2.13.1 Đa năng hó a các hàm (Functions overloading) : Trong ngôn ngữ C cũng như moị ngôn ngữ máy tính khác, mỗi hàm đều phải có môṭ tên phân biêṭ. Đôi khi đây là môṭ điều phiều toái. Chẳng haṇ như trong ngôn ngữ C, có rất nhiều hàm trả về tri ̣tuyêṭ đối của môṭ tham số là số, vi ̀ câǹ thiết phải có tên
  99. phân biêṭ nên C phải có hàm riêng cho mỗi kiểu dữ liêụ số, do vâỵ chúng ta có tới ba hàm khác nhau để trả về tri ̣ tuyêṭ đối của môṭ tham số : int abs(int i); long labs(long l); double fabs(double d); Tất cả các hàm này đều cùng thưc̣ hiêṇ môṭ chứ a năng nên chúng ta thấy điều này nghic̣ h lý khi phải có ba tên khác nhau. C++ giải quyết điều này băǹ g cách cho phép chúng ta taọ ra các hàm khác nhau có cùng môṭ tên. Đây chính là đa năng hóa hàm. Do đó trong C++ chúng ta có thể điṇ h nghiã laị các hàm trả về tri ̣ tuyêṭ đối để
  100. thay thế các hàm trên như sau : int abs(int i); long abs(long l); double abs(double d ) ; Ví dụ 2.16: CT2_16.CPP 1: #include 2: #include 3: 4: int MyAbs(int X); 5: long MyAbs(long X); 6: double MyAbs(double X); 7: 8: int main()
  101. 9: { 10: int X = -7; 11: long Y = 200000l; 12: double Z = -35.678; 13: cout<<"Tri tuyet doi cua so nguyen (int) "<<X<<" la " 14: <<MyAbs(X)<<endl; 15: cout<<"Tri tuyet doi cua so nguyen (long int) " <<Y<<" la " 16: <<MyAbs(Y)<<endl; 17: cout<<"Tri tuyet doi cua so thuc "<<Z<<" la " 18: <<MyAbs(Z)<<endl; 19: return 0; 20: } 21: 22: int MyAbs(int X) 23: { 24: return abs(X); 25: }
  102. 26: 27: long MyAbs(long X) 28: { 29: return labs(X); 30: } 31: 32: double MyAbs(double X) 33: { 34: return fabs(X); 35: } Chúng ta chaỵ ví du ̣ 2.16 , kết quả ở hiǹ h 2.19 Hiǹ h 2.19: Kết quả của ví du ̣2.16
  103. Triǹ h biên dic̣ h dưạ vào sư ̣ khác nhau về số các tham số, kiểu của các tham số để có thể xác điṇ h chính xác phiên bản cài đăṭ nào của hàm MyAbs() thích hơp̣ với môṭ lêṇ h goị hàm đươc̣ cho, chẳng haṇ như: MyAbs(-7); //Goị hàm int M y A b s ( i n t ) MyAbs(- 7l); //Goị hàm long MyAbs(long) MyAbs(-7.5); //Goị hàm double MyAbs(double) Quá triǹ h tim̀ đươc̣ hàm đươc̣ đa năng hóa cũng là quá triǹ h đươc̣ dùng để giải quyết các trường hơp̣ nhâp̣ nhăǹ g của C++. Chẳng haṇ như nếu tim̀ thấy môṭ phiên bản điṇ h nghiã nào đó của môṭ hàm đươc̣ đa năng hóa mà có kiểu dữ liêụ các tham số
  104. của nó trùng với kiểu các tham số đa ̃ gởi tới trong lêṇ h goị hàm thi ̀ phiên bản hàm đó sẽ đươc̣ goị . Nếu không triǹ h biên dic̣ h C++ sẽ goị đến phiên bản nào cho phép chuyển kiểu dễ dàng nhất. MyAbs(‘c’); //Goị int MyAbs(int) MyAbs(2.34f); //Goị double MyAbs(double) Các phép chuyển kiểu có săñ sẽ đươc̣ ưu tiên hơn các phép chuyển kiểu mà chúng ta taọ ra (chúng ta sẽ xem xét các phép chuyển kiểu tư ̣ taọ ở chương 3). Chúng ta cũng có thể lấy điạ chỉ của môṭ hàm đa ̃ đươc̣ đa năng hóa sao cho băǹ g môṭ cách nào đó chúng ta có thể làm cho triǹ h biên dic̣ h C++ biết đươc̣ chúng ta câǹ lấy điạ chỉ của phiên bản hàm nào có trong điṇ h nghiã . Chẳng haṇ như: int (*pf1)(int);
  105. long (*pf2)(long); int (*pf3)(double); pf1 = MyAbs; //Trỏ đến hàm int MyAbs(int) pf2 = MyAbs; //Trỏ đến hàm long MyAbs(long) pf3 = MyAbs; //Lỗi!!! (không có phiên bản hàm nào để đối sánh) Các giới haṇ của viêc̣ đa năng hóa các hàm: · Bất kỳ hai hàm nào trong tâp̣ các hàm đa ̃ đa năng phải có các tham số khác nhau. · Các hàm đa năng hóa với danh sách các tham số cùng kiểu chỉ dưạ trên kiểu trả về của hàm thi ̀ triǹ h biên dic̣ h báo lỗi. Chẳng haṇ như, các khai báo sau là không hơp̣ lê:̣ void Print(int X); int Print(int X);
  106. Không có cách nào để triǹ h biên dic̣ h nhâṇ biết phiên bản nào đươc̣ goị nếu giá tri ̣ trả về bi ̣ bỏ qua. Như vâỵ các phiên bản trong viêc̣ đa năng hóa phải có sự khác nhau ít nhất về kiểu hoăc̣ số tham số mà chúng nhâṇ đươc̣ . · Các khai báo băǹ g lêṇ h typedef không điṇ h nghiã kiểu mới. Chúng chỉ thay đổi tên goị của kiểu đa ̃ có. Chúng không ảnh hưởng tới cơ chế đa năng hóa hàm. Chúng ta haỹ xem xét đoaṇ ma ̃ sau: typedef char * PSTR; void Print(char * Mess); void Print(PSTR Mess); Hai hàm này có cùng danh sách các tham số, do đó đoaṇ ma ̃ trên sẽ phát sinh lỗi. · Đối với kiểu mảng và con trỏ đươc̣ xem như đồng nhất đối với sư ̣ phân biêṭ
  107. khác nhau giữa các phiên bản hàm trong viêc̣ đa năng hóa hàm. Chẳng haṇ như đoaṇ ma ̃ sau se phát sinh lỗi: void Print(char * Mess); void Print(char Mess[]); Tuy nhiên, đối với mảng nhiều chiều thi ̀ có sư ̣ phân biêṭ giữa các phiên bản hàm trong viêc̣ đa năng hóa hàm, chẳng haṇ như đoaṇ ma ̃ sau hơp̣ lê:̣ void Print(char Mess[]); void Print(char Mess[][7]); void Print(char Mess[][9][42]); · const và các con trỏ (hay các tham chiếu) có thể dùng để phân biêṭ, chẳng haṇ như đoaṇ ma ̃ sau hơp̣ lê:̣ void Print(char *Mess); void Print(const char *Mess); 2.2.13.2 Đa năng hó a các toán tử (Operators overloading) :
  108. Trong ngôn ngữ C, khi chúng ta tư ̣ taọ ra môṭ kiểu dữ liêụ mới, chúng ta thưc̣ hiêṇ các thao tác liên quan đến kiểu dữ liêụ đó thường thông qua các hàm, điều này trở nên không thoải mái. Ví du ̣ 2.17: Chương triǹ h cài đăṭ các phép toán côṇ g và trừ số phứ c CT2_17.CPP 1: #include 2: /* Điṇ h nghiã số phứ c */ 3: typedef struct 4: { 5: double Real; 6: double Imaginary; 7: }Complex; 8: 9: Complex SetComplex(double R,double I);
  109. 10: Complex AddComplex(Complex C1,Complex C2); 11: Complex SubComplex(Complex C1,Complex C2); 12: void DisplayComplex(Complex C); 13: 14: int main(void) 15: { 16: Complex C1,C2,C3,C4; 17: 18: C1 = SetComplex(1.0,2.0); 19: C2 = SetComplex(-3.0,4.0); 20: printf("\nSo phuc thu nhat:"); 21: DisplayComplex(C1); 22: printf("\nSo phuc thu hai:"); 23: DisplayComplex(C2); 24: C3 = AddComplex(C1,C2); //Hơi bất tiêṇ !!! 25: C4 = SubComplex(C1,C2); 26: printf("\nTong hai so phuc nay:"); 27: DisplayComplex(C3); 28: printf("\nHieu hai so phuc nay:");
  110. 29: DisplayComplex(C4); 30: return 0; 31: } 32: 33: /* Đăṭ giá tri ̣cho môṭ số phứ c */ 34: Complex SetComplex(double R,double I) 35: { 36: Complex Tmp; 37: 38: Tmp.Real = R; 39: Tmp.Imaginary = I; 40: return Tmp; 41: } 42: /* Côṇ g hai số phứ c */ 43: Complex AddComplex(Complex C1,Complex C2) 44: { 45: Complex Tmp; 46: 47: Tmp.Real = C1.Real+C2.Real;
  111. 48: Tmp.Imaginary = C1.Imaginary+C2.Imaginary; 49: return Tmp; 50: } 51: 52: /* Trừ hai số phứ c */ 53: Complex SubComplex(Complex C1,Complex C2) 54: { 55: Complex Tmp; 56: 57: Tmp.Real = C1.Real-C2.Real; 58: Tmp.Imaginary = C1.Imaginary-C2.Imaginary; 59: return Tmp; 60: } 61: 62: /* Hiển thi ̣số phứ c */ 63: void DisplayComplex(Complex C) 64: { 65: printf("(%.1lf,%.1lf)",C.Real,C.Imaginary); 66: }
  112. Chúng ta chaỵ ví du ̣ 2.17, kết quả ở hiǹ h 2.20 Hiǹ h 2.20: Kết quả của ví du ̣2.17 Trong chương triǹ h ở ví du ̣ 2.17, chúng ta nhâṇ thấy với các hàm vừa cài đăṭ dùng để côṇ g và trừ hai số phứ c 1+2i và – 3+4i; người lâp̣ triǹ h hoàn toàn không thoải mái khi sử duṇ g bởi vi ̀ thưc̣ chất thao tác côṇ g và trừ là các toán tử chứ không phải là hàm. Để khắc phuc̣ yếu điểm này, trong C++ cho phép chúng ta có thể điṇ h nghiã laị chứ c năng của các toán tử đa ̃ có
  113. săñ môṭ cách tiêṇ lơị và tư ̣ nhiên hơn rất nhiều. Điều này goị là đa năng hóa toán tử . Khi đó chương triǹ h ở ví du ̣ 2.17 đươc̣ viết như sau: Ví du ̣2.18: CT2_18.CPP 1: #include 2: // Điṇ h nghiã số phứ c 3: typedef struct 4: { 5: double Real; 6: double Imaginary; 7: }Complex; 8: 9: Complex SetComplex(double R,double I); 10: void DisplayComplex(Complex C); 11: Complex operator + (Complex C1,Complex C2); 12: Complex operator - (Complex C1,Complex C2);
  114. 13: 14: int main(void) 15: { 16: Complex C1,C2,C3,C4; 17: 18: C1 = SetComplex(1.0,2.0); 19: C2 = SetComplex(-3.0,4.0); 20: cout<<"\nSo phuc thu nhat:"; 21: DisplayComplex(C1); 22: cout<<"\nSo phuc thu hai:"; 23: DisplayComplex(C2); 24: C3 = C1 + C2; 25: C4 = C1 - C2; 26: cout<<"\nTong hai so phuc nay:"; 27: DisplayComplex(C3); 28: cout<<"\nHieu hai so phuc nay:"; 29: DisplayComplex(C4); 30: return 0; 31: }
  115. 32: 33: //Đăṭ giá tri ̣cho môṭ số phứ c 34: Complex SetComplex(double R,double I) 35: { 36: Complex Tmp; 37: 38: Tmp.Real = R; 39: Tmp.Imaginary = I; 40: return Tmp; 41: } 42: 43: //Côṇ g hai số phứ c 44: Complex operator + (Complex C1,Complex C2) 45: { 46: Complex Tmp; 47: 48: Tmp.Real = C1.Real+C2.Real; 49: Tmp.Imaginary = C1.Imaginary+C2.Imaginary; 50: return Tmp;
  116. 51: } 52: 53: //Trừ hai số phứ c 54: Complex operator - (Complex C1,Complex C2) 55: { 56: Complex Tmp; 57: 58: Tmp.Real = C1.Real-C2.Real; 59: Tmp.Imaginary = C1.Imaginary-C2.Imaginary; 60: return Tmp; 61: } 62: 63: //Hiển thi ̣số phứ c 64: void DisplayComplex(Complex C) 65: { 66: cout<<"("<<C.Real<<","<<C.Imaginary<<")"; 67: }
  117. Chúng ta chaỵ ví du ̣ 2.18, kết quả ở hiǹ h 2.21 Hiǹ h 2.21: Kết quả của ví du ̣2.18 Như vâỵ trong C++, các phép toán trên các giá tri ̣ kiểu số phứ c đươc̣ thưc̣ hiêṇ băǹ g các toán tử toán hoc̣ chuẩn chứ không phải băǹ g các tên hàm như trong C. Chẳng haṇ chúng ta có lêṇ h sau: C4 = AddComplex(C3, SubComplex(C1,C2)); thi ̀ ở trong C++, chúng ta có lêṇ h tương ứ ng như sau: C4 = C3 + C1 - C2; Chúng ta nhâṇ thấy răǹ g cả hai lêṇ h
  118. đều cho cùng kết quả nhưng lêṇ h của C++ thi ̀ dễ hiểu hơn. C++ làm đươc̣ điều này băǹ g cách taọ ra các hàm điṇ h nghiã cách thưc̣ hiêṇ của môṭ toán tử cho các kiểu dữ liêụ tư ̣ điṇ h nghiã . Môṭ hàm điṇ h nghiã môṭ toán tử có cú pháp sau: data_type operator operator_symbol ( parameters ) { } Trong đó: data_type: Kiểu trả về. operator_symbol: Ký hiêụ của toán tử . parameters: Các tham số (nếu có). Trong chương triǹ h ví du ̣ 2.18, toán tử + là toán tử gồm hai toán haṇ g (goị là toán tử hai ngôi; toán tử môṭ ngôi là toán tử chỉ có môṭ toán haṇ g) và triǹ h biên dic̣ h biết
  119. tham số đâù tiên là ở bên trái toán tử , còn tham số thứ hai thi ̀ ở bên phải của toán tử . Trong trường hơp̣ lâp̣ triǹ h viên quen thuôc̣ với cách goị hàm, C++ vâñ cho phép băǹ g cách viết như sau: C3 = operator + (C1,C2); C4 = operator - (C1,C2); Các toán tử đươc̣ đa năng hóa sẽ đươc̣ lưạ choṇ bởi triǹ h biên dic̣ h cũng theo cách thứ c tương tư ̣ như viêc̣ choṇ lưạ giữa các hàm đươc̣ đa năng hóa là khi găp̣ môṭ toán tử làm viêc̣ trên các kiểu không phải là kiểu có săñ , triǹ h biên dic̣ h sẽ tim̀ môṭ hàm điṇ h nghiã của toán tử nào đó có các tham số đối sánh với các toán haṇ g để dùng. Chúng ta sẽ tim̀ hiểu kỹ về viêc̣ đa năng hóa các toán tử trong chương 4. Các giới haṇ của đa năng hóa toán tử : · Chúng ta không thể điṇ h nghiã các
  120. toán tử mới. · Hâù hết các toán tử của C++ đều có thể đươc̣ đa năng hóa. Các toán tử sau không đươc̣ đa năng hóa là : Toán Ý nghiã tử :: Toán tử điṇ h phaṃ vi. Truy câp̣ đến con trỏ là .* trường của struct hay thành viên của class. Truy câp̣ đến trường của . struct hay thành viên của class. ?: Toán tử điều kiêṇ sizeof và chúng ta cũng không thể đa năng hóa bất kỳ ký hiêụ tiền xử lý nào.
  121. · Chúng ta không thể thay đổi thứ tư ̣ ưu tiên của môṭ toán tử hay không thể thay đổi số các toán haṇ g của nó. · Chúng ta không thể thay đổi ý nghiã của các toán tử khi áp duṇ g cho các kiểu có săñ . · Đa năng hóa các toán tử không thể có các tham số có giá tri ̣măc̣ điṇ h. Các toán tử có thể đa năng hoá: + - * / % ! = += ^= &= |= > = && || ++ () [] new delete & ~ *= /= %= >>= != , -> ->* Các toán tử đươc̣ phân loaị như sau :
  122. § Các toán tử môṭ ngôi : * & ~ ! ++ sizeof (data_type) Các toán tử này đươc̣ điṇ h nghiã chỉ có môṭ tham số và phải trả về môṭ giá tri ̣ cùng kiểu với tham số của chúng. Đối với toán tử sizeof phải trả về môṭ giá tri ̣kiểu size_t (điṇ h nghiã trong stddef.h) Toán tử (data_type) đươc̣ dùng để chuyển đổi kiểu, nó phải trả về môṭ giá tri ̣ có kiểu là data_type. § Các toán tử hai ngôi: * / % + - >> = >= <<= ^= |= Các toán tử gán đươc̣ điṇ h nghiã chỉ có môṭ tham số. Không có giới haṇ về kiểu
  123. của tham số và kiểu trả về của phép gán. § Toán tử lấy thành viên : -> § Toán tử lấy phâǹ tử theo chỉ số: [] § Toán tử goị hàm: () 3.1 DẪ N NHẬP Bây giờ chúng ta bắt đâù tim̀ hiểu về lâp̣ triǹ h hướng đối tươṇ g trong C++. Trong các phâǹ sau, chúng ta cũng tim̀ hiểu về các kỹ thuâṭ của thiết kế hướng đối tươṇ g (Object-Oriented Design OOD): Chúng ta phân tích môṭ vấn đề cu ̣ thể, xác điṇ h các đối tươṇ g nào câǹ để cài đăṭ hệ thống, xác điṇ h các thuôc̣ tính nào mà đối tươṇ g phải có, xác điṇ h hành vi nào mà đối tươṇ g câǹ đưa ra, và chỉ rõ làm thế nào các đối tươṇ g câǹ tương tác với đối tươṇ g khác để thưc̣ hiêṇ các muc̣ tiêu tổng thể của hê ̣thống. Chúng ta nhắc laị các khái niêṃ và
  124. thuâṭ ngữ chính của đính hướng đối tươṇ g. OOP đó ng gó i dữ liêụ (các thuôc̣ tính) và các hàm (hành vi) thành gói goị là cá c đố i tươṇ g. Dữ liêụ và các hàm của đối tươṇ g có sư ̣ liên hê ̣ mâṭ thiết với nhau. Các đối tươṇ g có các đăc̣ tính của viêc̣ che dấu thông tin. Điều này nghiã là măc̣ dù các đối tươṇ g có thể biết làm thế nào liên lac̣ với đối tươṇ g khác thông qua các giao diêṇ hoàn toàn xác điṇ h, biǹ h thường các đối tươṇ g không đươc̣ phép biết làm thế nào các đối tươṇ g khác đươc̣ thưc̣ thi, các chi tiết của sư ̣ thi hành đươc̣ dấu bên trong các đối tươṇ g. Trong C và các ngôn ngữ lâp̣ triǹ h thủ tuc̣ , lâp̣ triǹ h có khuynh hướng điṇ h hướng hành đôṇ g, trong khi ý tưởng trong lâp̣ triǹ h C++ là điṇ h hướng đối tươṇ g. Trong C, đơn vi ̣của lâp̣ triǹ h là hàm; trong C++,
  125. đơn vi ̣của lâp̣ triǹ h là lớ p (class) . Các lâp̣ triǹ h viên C tâp̣ trung vào viết các hàm. Các nhóm của các hành đôṇ g mà thưc̣ hiêṇ vài công viêc̣ đươc̣ taọ thành các hàm, và các hàm đươc̣ nhóm thành các chương triǹ h. Dữ liêụ thi ̀ rất quan troṇ g trong C, nhưng quan điểm là dữ liêụ tồn taị chính trong viêc̣ hỗ trơ ̣ các hàm đôṇ g mà hàm thưc̣ hiêṇ . Các đôṇ g từ trong môṭ hệ thống giúp cho lâp̣ triǹ h viên C xác điṇ h tâp̣ các hàm mà sẽ hoaṭ đôṇ g cùng với viêc̣ thưc̣ thi hê ̣thống. Các lâp̣ triǹ h viên C++ tâp̣ trung vào viêc̣ taọ ra "các kiểu do người dùng điṇ h nghiã " (user-defined types) goị là các lớp. Các lớp cũng đươc̣ tham chiếu như "các kiểu do lâp̣ triǹ h viên điṇ h nghiã " (programmer-defined types). Mỗi lớp chứ a dữ liêụ cũng như tâp̣ các hàm mà xử
  126. lý dữ liêụ . Các thành phâǹ dữ liêụ của môṭ lớp đươc̣ goị là "các thành viên dữ liêụ " (data members). Các thành phâǹ hàm của môṭ lớp đươc̣ goị là "các hàm thành viên" (member functions). Giống như thưc̣ thể của kiểu có săñ như int đươc̣ goị là môṭ biến, môṭ thưc̣ thể của kiểu do người dùng điṇ h nghiã (nghiã là môṭ lớp) đươc̣ goị là môṭ đối tươṇ g. Các danh từ trong môṭ hệ thống giúp cho lâp̣ triǹ h viên C++ xác điṇ h tâp̣ các lớp. Các lớp này đươc̣ sử duṇ g để taọ các đối tươṇ g mà sẽ sẽ hoaṭ đôṇ g cùng với viêc̣ thưc̣ thi hê ̣thống. Các lớp trong C++ đươc̣ tiến hóa tự nhiên của khái niêṃ struct trong C. Trước khi tiến hành viêc̣ triǹ h bày các lớp trong C++, chúng ta tim̀ hiểu về cấu trúc, và chúng ta xây dưṇ g môṭ kiểu do người dùng điṇ h nghiã dưạ trên môṭ cấu trúc.
  127. 3.2 CÀ I ĐẶT MỘT KIỂ U DO NGƯỜ I DÙ NG ĐIṆ H NGHIÃ VỚ I MỘT struct Ví du ̣ 3.1: Chúng ta xây dưṇ g kiểu cấu trú c Time với ba thành viên số nguyên: Hour, Minute và second. Chương triǹ h điṇ h nghiã môṭ cấu trúc Time goị là DinnerTime. Chương triǹ h in thời gian dưới daṇ g giờ quân đôị và daṇ g chuẩn. CT3_1.CPP 1: #include 2: 3: struct Time 4: { 5: int Hour; // 0-23 6: int Minute; // 0-59 7: int Second; // 0-59 8: };
  128. 9: 10: void PrintMilitary(const Time &); //prototype 11: void PrintStandard(const Time &); //prototype 12: 13: int main() 14: { 15: Time DinnerTime; 16: 17: //Thiết lâp̣ các thành viên với giá tri ̣hơp̣ lệ 18: DinnerTime.Hour = 18; 19: DinnerTime.Minute = 30; 20: DinnerTime.Second = 0; 21: 22: cout << "Dinner will be held at "; 23: PrintMilitary(DinnerTime); 24: cout << " military time," << endl << "which is "; 25: PrintStandard(DinnerTime); 26: cout << " standard time." << endl; 27:
  129. 28: //Thiết lâp̣ các thành viên với giá tri ̣không hơp̣ lệ 29: DinnerTime.Hour = 29; 30: DinnerTime.Minute = 73; 31: DinnerTime.Second = 103; 32: 33: cout << endl << "Time with invalid values: "; 34: PrintMilitary(DinnerTime); 35: cout << endl; 36: return 0; 37: } Chúng ta chaỵ ví du ̣3.1, kết quả ở hiǹ h 3.1
  130. Hiǹ h 3.1: Kết quả của ví du ̣3.1 Có môṭ vài haṇ chế khi taọ các kiểu dữ liêụ mới với các cấu trúc ở phâǹ trên. Khi viêc̣ khởi taọ không đươc̣ yêu câù , có thể có dữ liêụ chưa khởi taọ và các vấn đề nảy sinh. Ngay cả nếu dữ liêụ đươc̣ khởi taọ , nó có thể khởi taọ không chính xác. Các giá tri ̣ không hơp̣ lê ̣ có thể đươc̣ gán cho các thành viên của môṭ cấu trúc bởi vì chương triǹ h trưc̣ tiếp truy câp̣ dữ liêụ . Chẳng haṇ ở ví du ̣ 3.1 ở dòng 29 đến dòng 31, chương triǹ h gán các giá tri ̣không hơp̣ lê ̣ cho đối tươṇ g DinnerTime. Nếu viêc̣ cài đăṭ của struct thay đổi, tất cả các
  131. chương triǹ h sử duṇ g struct phải thay đổi. Điều này do lâp̣ triǹ h viên trưc̣ tiếp thao tác kiểu dữ liêụ . Không có "giao diêṇ " để bảo đảm lâp̣ triǹ h viên sử duṇ g dữ liêụ chính xác và bảo đảm dữ liêụ còn laị ở traṇ g thái thích hơp̣ . Măṭ khác, cấu trúc trong C không thể đươc̣ in như môṭ đơn vi,̣ chúng đươc̣ in khi các thành viên đươc̣ in. Các cấu trúc trong C không thể so sánh với nhau, chúng phải đươc̣ so sánh thành viên với thành viên. Phâǹ sau cài đăṭ laị cấu trúc Time ở ví du ̣ 3.1 như môṭ lớp và chứ ng minh môṭ số thuâṇ lơị để viêc̣ taọ ra cái goị là các kiểu dữ liêụ trừu tươṇ g (Abstract Data Types – ADT) như các lớp. Chúng ta sẽ thấy răǹ g các lớp và các cấu trúc có thể sử duṇ g gâǹ như giống nhau trong C++. Sư ̣ khác nhau giữa chúng là thuôc̣ tính truy câp̣ các thành
  132. viên. 3.2 CÀ I ĐẶT MỘT KIỂ U DO NGƯỜ I DÙ NG ĐIṆ H NGHIÃ VỚ I MỘT struct Ví du ̣ 3.1: Chúng ta xây dưṇ g kiểu cấu trú c Time với ba thành viên số nguyên: Hour, Minute và second. Chương triǹ h điṇ h nghiã môṭ cấu trúc Time goị là DinnerTime. Chương triǹ h in thời gian dưới daṇ g giờ quân đôị và daṇ g chuẩn. CT3_1.CPP 1: #include 2: 3: struct Time 4: { 5: int Hour; // 0-23 6: int Minute; // 0-59 7: int Second; // 0-59
  133. 8: }; 9: 10: void PrintMilitary(const Time &); //prototype 11: void PrintStandard(const Time &); //prototype 12: 13: int main() 14: { 15: Time DinnerTime; 16: 17: //Thiết lâp̣ các thành viên với giá tri ̣hơp̣ lệ 18: DinnerTime.Hour = 18; 19: DinnerTime.Minute = 30; 20: DinnerTime.Second = 0; 21: 22: cout << "Dinner will be held at "; 23: PrintMilitary(DinnerTime); 24: cout << " military time," << endl << "which is "; 25: PrintStandard(DinnerTime); 26: cout << " standard time." << endl;
  134. 27: 28: //Thiết lâp̣ các thành viên với giá tri ̣không hơp̣ lệ 29: DinnerTime.Hour = 29; 30: DinnerTime.Minute = 73; 31: DinnerTime.Second = 103; 32: 33: cout << endl << "Time with invalid values: "; 34: PrintMilitary(DinnerTime); 35: cout << endl; 36: return 0; 37: } Chúng ta chaỵ ví du ̣3.1, kết quả ở hiǹ h 3.1
  135. Hiǹ h 3.1: Kết quả của ví du ̣3.1 Có môṭ vài haṇ chế khi taọ các kiểu dữ liêụ mới với các cấu trúc ở phâǹ trên. Khi viêc̣ khởi taọ không đươc̣ yêu câù , có thể có dữ liêụ chưa khởi taọ và các vấn đề nảy sinh. Ngay cả nếu dữ liêụ đươc̣ khởi taọ , nó có thể khởi taọ không chính xác. Các giá tri ̣ không hơp̣ lê ̣ có thể đươc̣ gán cho các thành viên của môṭ cấu trúc bởi vì chương triǹ h trưc̣ tiếp truy câp̣ dữ liêụ . Chẳng haṇ ở ví du ̣ 3.1 ở dòng 29 đến dòng 31, chương triǹ h gán các giá tri ̣không hơp̣ lê ̣ cho đối tươṇ g DinnerTime. Nếu viêc̣ cài đăṭ của struct thay đổi, tất cả các
  136. chương triǹ h sử duṇ g struct phải thay đổi. Điều này do lâp̣ triǹ h viên trưc̣ tiếp thao tác kiểu dữ liêụ . Không có "giao diêṇ " để bảo đảm lâp̣ triǹ h viên sử duṇ g dữ liêụ chính xác và bảo đảm dữ liêụ còn laị ở traṇ g thái thích hơp̣ . Măṭ khác, cấu trúc trong C không thể đươc̣ in như môṭ đơn vi,̣ chúng đươc̣ in khi các thành viên đươc̣ in. Các cấu trúc trong C không thể so sánh với nhau, chúng phải đươc̣ so sánh thành viên với thành viên. Phâǹ sau cài đăṭ laị cấu trúc Time ở ví du ̣ 3.1 như môṭ lớp và chứ ng minh môṭ số thuâṇ lơị để viêc̣ taọ ra cái goị là các kiểu dữ liêụ trừu tươṇ g (Abstract Data Types – ADT) như các lớp. Chúng ta sẽ thấy răǹ g các lớp và các cấu trúc có thể sử duṇ g gâǹ như giống nhau trong C++. Sư ̣ khác nhau giữa chúng là thuôc̣ tính truy câp̣ các thành
  137. viên. 3.3 CÀ I ĐẶT MỘT KIỂ U DỮ LIÊỤ TRỪ U TƯỢNG VỚ I MỘT LỚ P Các lớp cho phép lâp̣ triǹ h viên mô hiǹ h các đối tươṇ g mà có các thuôc̣ tính (biểu diễn như các thành viên dữ liêụ – Data members) và các hành vi hoăc̣ các thao tác (biểu diễn như các hàm thành viên – Member functions). Các kiểu chứ a các thành viên dữ liêụ và các hàm thành viên đươc̣ điṇ h nghiã thông thường trong C++ sử duṇ g từ khóa class, có cú pháp như sau: class { //Thân của lớp }; Trong đó: class-name: tên lớp. member-list: đăc̣ tả các thành viên dữ
  138. liêụ và các hàm thành viên. Các hàm thành viên đôi khi đươc̣ goị là các phương thứ c (methods) trong các ngôn ngữ lâp̣ triǹ h hướng đối tươṇ g khác, và đươc̣ đưa ra trong viêc̣ đáp ứ ng các message gởi tới môṭ đối tươṇ g. Môṭ message tương ứ ng với viêc̣ goị hàm thành viên. Khi môṭ lớp đươc̣ điṇ h nghiã , tên lớp có thể đươc̣ sử duṇ g để khai báo đối tươṇ g của lớp theo cú pháp sau: ; Chẳng haṇ , cấu trúc Time sẽ đươc̣ điṇ h nghiã dưới daṇ g lớp như sau: class Time { public: Time(); void SetTime(int, int, int)
  139. void PrintMilitary(); void PrintStandard() private: int Hour; // 0 - 23 int Minute; // 0 - 59 int Second; // 0 - 59 }; Trong điṇ h nghiã lớp Time chứ a ba thành viên dữ liêụ là Hour, Minute và Second, và cũng trong lớp này, chúng ta thấy các nhañ public và private đươc̣ goị là các thuôc̣ tính xác điṇ h truy câp̣ thành viên (member access specifiers) goị tắt là thuôc̣ tính truy câp̣ . Bất kỳ thành viên dữ liêụ hay hàm thành viên khai báo sau public có thể đươc̣ truy câp̣ bất kỳ nơi nào mà chương triǹ h truy câp̣ đến môṭ đối tươṇ g của lớp. Bất kỳ thành viên dữ liêụ hay hàm thành viên khai
  140. báo sau private chỉ có thể đươc̣ truy câp̣ bởi các hàm thành viên của lớp. Các thuôc̣ tính truy câp̣ luôn luôn kết thúc với dấu hai chấm (:) và có thể xuất hiêṇ nhiều lâǹ và theo thứ tư ̣ bất kỳ trong điṇ h nghiã lớp. Măc̣ điṇ h thuôc̣ tính truy câp̣ là private. Điṇ h nghiã lớp chứ a các prototype của bốn hàm thành viên sau thuôc̣ tính truy câp̣ p u b l i c l à Time( ) , SetTime(), PrintMilitary() và PrintStandard(). Đó là các hàm thành viên public (public member function) hoăc̣ giao diêṇ (interface) của lớp. Các hàm này sẽ đươc̣ sử duṇ g bởi các client (nghiã là các phâǹ của môṭ chương triǹ h mà là các người dùng) của lớp xử lý dữ liêụ của lớp. Có thể nhâṇ thấy trong điṇ h nghiã lớp Time, hàm thành viên Time() có cùng tên với tên lớ p Time, nó đươc̣ goị là hàm xây dưṇ g
  141. (constructor function) của lớp Time. Môṭ constructor là môṭ hàm thành viên đăc̣ biêṭ mà khởi đôṇ g các thành viên dữ liêụ của môṭ đối tươṇ g của lớp. Môṭ constructor của lớp đươc̣ goị tư ̣ đôṇ g khi đối tươṇ g của lớp đó đươc̣ taọ . Thông thường, các thành viên dữ liêụ đươc̣ liêṭ kê trong phâǹ private của môṭ lớp, còn các hàm thành viên đươc̣ liêṭ kê trong phâǹ public. Nhưng có thể có các hàm thành viên private và thành viên dữ liêụ public. Khi lớp đươc̣ điṇ h nghiã , nó có thể sử duṇ g như môṭ kiểu trong phâǹ khai báo như sau: Time Sunset, // Đối tươṇ g của lớp Time ArrayTimes[5], // Mảng các đối tươṇ g của lớp Time
  142. *PTime, // Con trỏ trỏ đến môṭ đối tươṇ g của lớp Time &DinnerTime = Sunset; // Tham chiếu đến môṭ đối tươṇ g của lớp Time Ví du ̣ 3.2: Xây dưṇ g laị lớp Time ở ví du ̣3.1 CT3_2.CPP 1: #include 2: 3: class Time 4: { 5: public: 6: Time(); //Constructor 7: void SetTime(int, int, int); //Thiết lâp̣ Hour, Minute va Second 8: void PrintMilitary(); //In thời gian dưới daṇ g giờ quân đôị 9: void PrintStandard(); //In thời gian dưới daṇ g chuẩn
  143. 10: private: 11: int Hour; // 0 - 23 12: int Minute; // 0 - 59 13: int Second; // 0 - 59 14: }; 15: 16: //Constructor khởi taọ mỗi thành viên dữ liêụ với giá tri ̣zero 17: //Bảo đảm tất cả các đối tươṇ g bắt đâù ở môṭ traṇ g thái thích hơp̣ 18: Time::Time() 19: { 20: Hour = Minute = Second = 0; 21: } 22: 23: //Thiết lâp̣ môṭ giá tri ̣Time mới sử duṇ g giờ quânđôị 24: //Thưc̣ hiêṇ viêc̣ kiểm tra tính hơp̣ lê ̣trên các giá tri ̣ dữ liêụ 25: //Thiết lâp̣ các giá tri ̣không hơp̣ lê ̣thành zero
  144. 26: void Time::SetTime(int H, int M, int S) 27: { 28: Hour = (H >= 0 && H = 0 && M = 0 && S < 60) ? S : 0; 31: } 32: 33: //In thời gian dưới daṇ g giờ quân đôị 34: void Time::PrintMilitary() 35: { 36: cout << (Hour < 10 ? "0" : "") << Hour << ":" 37: << (Minute < 10 ? "0" : "") << Minute << ":" 38: << (Second < 10 ? "0" : "") << Second; 39: } 40: 41: //In thời gian dưới daṇ g chuẩn 42: void Time::PrintStandard() 43: { 44: cout << ((Hour == 0 || Hour == 12) ? 12 : Hour %
  145. 12) 44: << ":" << (Minute < 10 ? "0" : "") << Minute 45: << ":" << (Second < 10 ? "0" : "") << Second 46: << (Hour < 12 ? " AM" : " PM"); 48: } 49: 50: int main() 51: { 52: Time T; //Đối tươṇ g T của lớp Time 53: 54: cout << "The initial military time is "; 55: T.PrintMilitary(); 56: cout << endl << "The initial standard time is "; 57: T.PrintStandard(); 58: 59: T.SetTime(13, 27, 6); 60: cout << endl << endl << "Military time after SetTime is "; 61: T.PrintMilitary();
  146. 62: cout << endl << "Standard time after SetTime is "; 63: T.PrintStandard(); 64: 65: T.SetTime(99, 99, 99); //Thử thiết lâp̣ giá tri ̣không hơp̣ lệ 66: cout << endl << endl << "After attempting invalid settings:" 67: << endl << "Military time: "; 68: T.PrintMilitary(); 69: cout << endl << "Standard time: "; 70: T.PrintStandard(); 71: cout << endl; 72: return 0; 73: } Chúng ta chaỵ ví du ̣3.2, kết quả ở hiǹ h 3.2
  147. Hiǹ h 3.2: Kết quả của ví du ̣3.2 Trong ví du ̣ 3.2, chương triǹ h thuyết minh môṭ đối tươṇ g của lớp Time goị là T (dòng 52). Khi đó constructor của lớp Time tư ̣ đôṇ g goị và rõ ràng khởi taọ mỗi thành viên dữ liêụ private là zero. Sau đó thời gian đươc̣ in dưới daṇ g giờ quân đôị và daṇ g chuẩn để xác nhâṇ các thành viên này đươc̣ khởi taọ thích hơp̣ (dòng 54 đến 57). Kế tới thời gian đươc̣ thiết lâp̣ băǹ g
  148. cách sử duṇ g hàm thành viên SetTime() (dòng 59) và thời gian laị đươc̣ in ở hai daṇ g (dòng 60 đến 63). Cuối cùng hàm thành viên SetTime() (dòng 65) thử thiết lâp̣ các thành viên dữ liêụ với các giá tri ̣ không hơp̣ lê,̣ và thời gian laị đươc̣ in ở hai daṇ g (dòng 66 đến 70). Chúng ta nhâṇ thấy răǹ g, tất cả các thành viên dữ liêụ của môṭ lớp không thể khởi taọ taị nơi mà chúng đươc̣ khai báo trong thân lớp. Các thành viên dữ liêụ này phải đươc̣ khởi taọ bởi constructor của lớp hay chúng có thể gán giá tri ̣ bởi các hàm thiết lâp̣ . Khi môṭ lớp đươc̣ điṇ h nghiã và các hàm thành viên của nó đươc̣ khai báo, các hàm thành viên này phải đươc̣ điṇ h nghiã . Mỗi hàm thành viên của lớp có thể đươc̣ điṇ h nghiã trưc̣ tiếp trong thân lớp (hiển
  149. nhiên bao gồm prototype hàm của lớp), hoăc̣ hàm thành viên có thể đươc̣ điṇ h nghiã sau thân lớp. Khi môṭ hàm thành viên đươc̣ điṇ h nghiã sau điṇ h nghiã lớp tương ứ ng, tên hàm đươc̣ đăṭ trước bởi tên lớp và toán tử điṇ h phaṃ vi (::). Chẳng haṇ như ở ví du ̣ 3.2 gồm các dòng 18, 26, 34 và 42. Bởi vi ̀ các lớp khác nhau có thể có các tên thành viên giống nhau, toán tử điṇ h phaṃ vi "ràng buôc̣ " tên thành viên tới tên lớp để nhâṇ daṇ g các hàm thành viên của môṭ lớp. Măc̣ dù môṭ hàm thành viên khai báo trong điṇ h nghiã môṭ lớp có thể điṇ h nghiã bên ngoài điṇ h nghiã lớp này, hàm thành viên đó vâñ còn bên trong phaṃ vi của lớp, nghiã là tên của nó chỉ đươc̣ biết tới các thành viên khác của lớp ngoaị trừ tham chiếu thông qua môṭ đối tươṇ g của lớp,
  150. môṭ tham chiếu tới môṭ đối tươṇ g của lớp, hoăc̣ môṭ con trỏ trỏ tới môṭ đối tươṇ g của lớp. Nếu môṭ hàm thành viên đươc̣ điṇ h nghiã trong điṇ h nghiã môṭ lớp, hàm thành viên này chính là hàm inline. Các hàm thành viên điṇ h nghiã bên ngoài điṇ h nghiã môṭ lớp có thể là hàm inline băǹ g cách sử duṇ g từ khóa inline. Hàm thành viên cùng tên với tên lớp nhưng đăṭ trước là môṭ ký tư ̣ nga ̃ (~) đươc̣ goị là destructor của lớp này. Hàm destructor làm "công viêc̣ nôị trơ ̣ kết thúc" trên mỗi đối tươṇ g của lớp trước khi vùng nhờ cho đối tươṇ g đươc̣ phuc̣ hồi bởi hệ thống. Ví du ̣ 3.3: Lấy laị ví du ̣ 3.2 nhưng hai hàm PrintMilitary() và PrintStandard() là các hàm inline.
  151. A.CPP 1: #include 2: 3: class Time 4: { 5: public: 6: Time(); ; //Constructor 7: void SetTime(int, int, int); // Thiết lâp̣ Hour, Minute va Second 8: void PrintMilitary() // In thời gian dưới daṇ g giờ quânđôị 9: { 10: cout << (Hour < 10 ? "0" : "") << Hour << ":" 11: << (Minute < 10 ? "0" : "") << Minute << ":" 12: << (Second < 10 ? "0" : "") << Second; 13: } 14: void PrintStandard(); // In thời gian dưới daṇ g chuẩn 15: private:
  152. 16: int Hour; // 0 - 23 17: int Minute; // 0 - 59 18: int Second; // 0 - 59 19: }; 20: //Constructor khởi taọ mỗi thành viên dữ liêụ với giá tri ̣zero 21: //Bảo đảm tất cả các đối tươṇ g bắt đâù ở môṭ traṇ g thái thích hơp̣ 22: Time::Time() 23: { 24: Hour = Minute = Second = 0; 25: } 26: 27: #9; //Thiết lâp̣ môṭ giá tri ̣Time mới sử duṇ g giờ quân đôị 28: #9; //Thưc̣ hiêṇ viêc̣ kiểm tra tính hơp̣ lê ̣trên các giá tri ̣dữ liêụ 29: #9; //Thiết lâp̣ các giá tri ̣không hơp̣ lê ̣thành zero 30: void Time::SetTime(int H, int M, int S)
  153. 31: { 32: Hour = (H >= 0 && H = 0 && M = 0 && S < 60) ? S : 0; 35: } 36: 37: #9; //In thời gian dưới daṇ g chuẩn 38: inline void Time::PrintStandard() 39: { 40: cout << ((Hour == 0 || Hour == 12) ? 12 : Hour % 12) 41: << ":" << (Minute < 10 ? "0" : "") << Minute 42: << ":" << (Second < 10 ? "0" : "") << Second 43: << (Hour < 12 ? " AM" : " PM"); 44: } 45: 46: int main() 47: { 48: Time T;
  154. 49: 50: cout << "The initial military time is "; 51: T.PrintMilitary(); 52: cout << endl << "The initial standard time is "; 53: T.PrintStandard(); 54: 55: T.SetTime(13, 27, 6); 56: cout << endl << endl << "Military time after SetTime is "; 57: T.PrintMilitary(); 58: cout << endl << "Standard time after SetTime is "; 59: T.PrintStandard(); 60: 61: T.SetTime(99, 99, 99); //Thử thiết lâp̣ giá tri ̣không hơp̣ lệ 62: cout << endl << endl << "After attempting invalid settings:" 63: << endl << "Military time: "; 64: T.PrintMilitary();
  155. 65: cout << endl << "Standard time: "; 66: T.PrintStandard(); 67: cout << endl; 68: return 0; 69: } Chúng ta chaỵ ví du ̣3.3, kết quả ở hiǹ h 3.3
  156. Hiǹ h 3.3: Kết quả của ví du ̣3.3 3.4 PHẠM VI LỚ P VÀ TRUY CẬP CÁ C THÀ NH VIÊN LỚ P Các thành viên dữ liêụ của môṭ lớp (các biến khai báo trong điṇ h nghiã lớp) và các hàm thành viên (các hàm khai báo trong điṇ h nghiã lớp) thuôc̣ vào phaṃ vi của lớp. Trong môṭ phaṃ vi lớp, các thành viên của lớp đươc̣ truy câp̣ ngay lâp̣ tứ c bởi tất cả các hàm thành viên của lớp đó và có thể đươc̣ tham chiếu môṭ cách dễ dàng bởi tên. Bên ngoài môṭ phaṃ vi lớp, các thành viên của lớp đươc̣ tham chiếu thông qua hoăc̣ môṭ tên đối tươṇ g, môṭ tham chiếu đến môṭ đối tươṇ g, hoăc̣ môṭ con trỏ tới đối tươṇ g. Các hàm thành viên của lớp có thể đươc̣ đa năng hóa (overload), nhưng chỉ
  157. bởi các hàm thành viên khác của lớp. Để đa năng hóa môṭ hàm thành viên, đơn giản cung cấp trong điṇ h nghiã lớp môṭ prototype cho mỗi phiên bản của hàm đa năng hóa, và cung cấp môṭ điṇ h nghiã hàm riêng biêṭ cho mỗi phiên bản của hàm. Các hàm thành viên có phaṃ vi hàm trong môṭ lớp – các biến điṇ h nghiã trong môṭ hàm thành viên chỉ đươc̣ biết tới hàm đó. Nếu môṭ hàm thành viên điṇ h nghiã môṭ biến cùng tên với tên môṭ biến trong phaṃ vi lớp, biến phaṃ vi lớp đươc̣ dấu bởi biến phaṃ vi hàm bên trong phaṃ vi hàm. Như thế môṭ biến bi ̣dấu có thể đươc̣ truy câp̣ thông qua toán tử điṇ h phaṃ vi. Các toán tử đươc̣ sử duṇ g để truy câp̣ các thành viên của lớp đươc̣ đồng nhất với các toán tử sử duṇ g để truy câp̣ các thành viên của cấu trúc. Toán tử lưạ choṇ thành
  158. viên dấu chấm (.) đươc̣ kết hơp̣ với môṭ tên của đối tươṇ g hay với môṭ tham chiếu tới môṭ đối tươṇ g để truy câp̣ các thành viên của đối tươṇ g. Toán tử lưạ choṇ thành viên mũi tên (->)đươc̣ kết hơp̣ với môṭ con trỏ trỏ tới môṭ truy câp̣ để truy câp̣ các thành viên của đối tươṇ g. Ví du ̣ 3.4: Chương triǹ h sau minh hoạ viêc̣ truy câp̣ các thành viên của môṭ lớp với các toán tử lưạ choṇ thành viên. CT3_4.CPP 1: #include 2: 3: class Count 4: {
  159. 5: public: 6: int X; 7: void Print() 8: { 9: cout << X << endl; 10: } 11: }; 12: 13: int main() 14: { 15: Count Counter, //Taọ đối tươṇ g Counter 16: *CounterPtr = &Counter, //Con trỏ trỏ tới Counter 17: &CounterRef = Counter; //Tham chiếu tới Counter 18: 19: cout << "Assign 7 to X and Print using the object's name: "; 20: Counter.X = 7; //Gán 7 cho thành viên dữ liêụ X 21: Counter.Print(); //Goị hàm thành viên Print 22:
  160. 23: cout X = 10; // Gán 10 cho thành viên dữ liêụ X 29: CounterPtr->Print(); //Goị hàm thành viên Print 30: return 0; 31: } Chúng ta chaỵ ví du ̣3.4, kết quả ở hiǹ h 3.4
  161. Hiǹ h 3.4: Kết quả của ví du ̣3.4 3.5 ĐIỀU KHIỂ N TRUY CẬP TỚ I CÁ C THÀ NH VIÊN Các thuôc̣ tính truy câp̣ public và private (và protected chúng ta sẽ xem xét sau) đươc̣ sử duṇ g để điều khiển truy câp̣ tới các thành viên dữ liêụ và các hàm thành viên của lớp. Chế đô ̣ truy câp̣ măc̣ điṇ h đối với lớp là private vi ̀ thế tất cả các thành viên sau phâǹ header của lớp và trước nhañ đâù tiên là private. Sau mỗi nhañ , chế đô ̣ mà đươc̣ kéo theo bởi nhañ đó áp duṇ g cho đến khi găp̣ nhañ kế tiếp hoăc̣ cho đến khi găp̣ dấu móc phải (}) của phâǹ điṇ h nghiã lớp. Các nhañ public,
  162. private và protected có thể đươc̣ lăp̣ laị nhưng cách dùng như vâỵ thi ̀ hiếm có và có thể gây khó hiểu. Các thành viên private chỉ có thể đươc̣ truy câp̣ bởi các hàm thành viên (và các hàm friend) của lớp đó. Các thành viên public của lớp có thể đươc̣ truy câp̣ bởi bất kỳ hàm nào trong chương triǹ h. Muc̣ đích chính của các thành viên public là để biểu thi ̣ cho client của lớp môṭ cái nhiǹ của các dic̣ h vu ̣(services) mà lớp cung cấp. Tâp̣ hơp̣ này của các dic̣ h vụ hiǹ h thành giao diêṇ public của lớp. Các client của lớp không câǹ quan tâm làm thế nào lớp hoàn thành các thao tác của nó. Các thành viên private của lớp cũng như các điṇ h nghiã của các hàm thành viên public của nó thi ̀ không phải có thể truy câp̣ tới client của môṭ lớp. Các thành phâǹ
  163. này hiǹ h thành sư ̣ thi hành của lớp. Ví du ̣ 3.5: Chương triǹ h sau cho thấy răǹ g các thành viên private chỉ có thể truy câp̣ thông qua giao diêṇ public sử duṇ g các hàm thành viên public. CT3_5.CPP 1: #include 2: 3: class MyClass 4: { 5: private: 6: int X,Y; 7: public: 8: void Print(); 9: };
  164. 10: 11: void MyClass::Print() 12: { 13: cout <<X<<Y<<endl; 14: } 15: 16: int main() 17: { 18: MyClass M; 19: 20: M.X = 3; //Error: 'MyClass::X' is not accessible 21: M.Y = 4; //Error: 'MyClass::Y' is not accessible 22: M.Print(); 23: return 0; 24: } Khi chúng ta biên dic̣ h chương triǹ h
  165. này, compiler phát sinh ra hai lỗi taị hai dòng 20 và 21 như sau: Hiǹ h 3.5: Thông báo lỗi của ví du ̣3.5 Thuôc̣ tính truy câp̣ măc̣ điṇ h đối với các thành viên của lớp là private. Thuôc̣ tính truy câp̣ các thành viên của môṭ lớp có thể đươc̣ thiết lâp̣ rõ ràng là public, protected hoăc̣ private. Thuôc̣ tính truy câp̣ măc̣ điṇ h đối với các thành viên của struct l à public. Thuôc̣ tính truy câp̣ các thành viên của môṭ struct cũng có thể đươc̣ thiết lâp̣ rõ ràng là public, protected hoăc̣ private. Truy
  166. câp̣ đến môṭ dữ liêụ private câǹ phải đươc̣ điều khiển cẩn thâṇ bởi viêc̣ sử duṇ g của các hàm thành viên, goị là các hàm truy câp̣ (access functions). 3.6 CÁ C HÀ M TRUY CẬP VÀ CÁ C HÀ M TIÊṆ ÍCH Không phải tất cả các hàm thành viên đều là public để phuc̣ vu ̣ như bô ̣ phâṇ giao diêṇ của môṭ lớp. Môṭ vài hàm còn laị là private và phuc̣ vu ̣ như các hàm tiêṇ ích (utility functions) cho các hàm khác của lớp. Các hàm truy câp̣ có thể đoc̣ hay hiển thi ̣ dữ liêụ . Sử duṇ g các hàm truy câp̣ để kiểm tra tính đúng hoăc̣ sai của các điều kiêṇ – các hàm như thế thường đươc̣ goị là các hàm khẳng điṇ h (predicate functions). Môṭ ví du ̣ của hàm khẳng điṇ h là môṭ hàm IsEmpty() của lớp container - môṭ lớp có
  167. khả năng giữ nhiều đối tươṇ g - giống như môṭ danh sách liên kết, môṭ stack hay môṭ hàng đơị . Môṭ chương triǹ h sẽ kiểm tra hàm IsEmpty() trước khi thử đoc̣ muc̣ khác từ đối tươṇ g container.Môṭ hàm tiêṇ ích không là môṭ phâǹ của môṭ giao diêṇ của lớp. Hơn nữa nó là môṭ hàm thành viên private mà hỗ trơ ̣ các thao tác của các hàm thành viên public. Các hàm tiêṇ ích không dư ̣ điṇ h đươc̣ sử duṇ g bởi các client của lớp. Ví du ̣3.6: Minh hoạ cho các hàm tiêṇ ích. CT3_6.CPP 1: #include
  168. 2: #include 3: 4: class SalesPerson 5: { 6: public: 7: SalesPerson(); //constructor 8: void SetSales(int, double);//Người dùng cung cấp các hiǹ h của 9: #9; #9; //những hàng bán của môṭ tháng 10: void PrintAnnualSales(); 11: 12: private: 13: double Sales[12]; //12 hiǹ h của những hàng bán hăǹ g tháng 14: double TotalAnnualSales(); //Hàm tiêṇ ích 15: }; 16: 17: //Hàm constructor khởi taọ mảng
  169. 18: SalesPerson::SalesPerson() 19: { 20: for (int I = 0; I = 1 && Month 0) 28: Sales[Month - 1] = Amount; 29: else 30: cout << "Invalid month or sales figure" << endl; 31: } 32: 33: //Hàm tiêṇ íchđể tính tổng hàng bán hăǹ g năm 34: double SalesPerson::TotalAnnualSales()
  170. 35: { 36: double Total = 0.0; 37: 38: for (int I = 0; I < 12; I++) 39: Total += Sales[I]; 40: return Total; 41: } 42: 43: //In tổng hàng bán hăǹ g năm 44: void SalesPerson::PrintAnnualSales() 45: { 46: cout << setprecision(2) 47: << setiosflags(ios::fixed | ios::showpoint) 48: << endl << "The total annual sales are: $" 49: << TotalAnnualSales() << endl; 50: } 51: 52: int main() 53: {
  171. 54: SalesPerson S; 55: double salesFigure; 56: 57: for (int I = 1; I > salesFigure; 61: S.SetSales(I, salesFigure); 62: } 63: S.PrintAnnualSales(); 64: return 0; 65: } Chúng ta chaỵ ví du ̣ 3.6 , kết quả ở hiǹ h 3.6
  172. Hiǹ h 3.6: Kết quả của ví du ̣3.6 3.7 KHỞ I ĐỘNG CÁ C ĐỐ I TƯỢNG CỦ A LỚ P : CONSTRUCTOR Khi môṭ đối tươṇ g đươc̣ taọ , các thành viên của nó có thể đươc̣ khởi taọ bởi môṭ
  173. hàm constructor. Môṭ constructor là môṭ hàm thành viên với tên giống như tên của lớp. Lâp̣ triǹ h viên cung cấp constructor mà đươc̣ goị tư ̣ đôṇ g mỗi khi đối tươṇ g của lớp đó đươc̣ taọ . Các thành viên dữ liêụ của môṭ lớp không thể đươc̣ khởi taọ trong điṇ h nghiã của lớp. Hơn nữa, các thành viên dữ liêụ phải đươc̣ khởi đôṇ g hoăc̣ trong môṭ constructor của lớp hoăc̣ các giá tri ̣của chúng có thể đươc̣ thiết lâp̣ sau sau khi đối tươṇ g đươc̣ taọ . Các constructor không thể mô tả các kiểu trả về hoăc̣ các giá tri ̣trả về. Các constructor có thể đươc̣ đa năng hóa để cung cấp sư ̣ đa daṇ g để khởi taọ các đối tươṇ g của lớp. Constructor có thể chứ a các tham số măc̣ điṇ h. Băǹ g cách cung cấp các tham số măc̣ điṇ h cho constructor, ngay cả nếu không có