Bài giảng Lập trình hướng đối tượng và C++ - Trường Đại học Hàng Hải

pdf 169 trang Gia Huy 17/05/2022 4950
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 và C++ - Trường Đại học Hàng Hải", để 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_va_c_truong_dai_hoc_hang.pdf

Nội dung text: Bài giảng Lập trình hướng đối tượng và C++ - Trường Đại học Hàng Hải

  1. BỘ GIAO THÔNG VẬN TẢI TRƢỜNG ĐẠI HỌC HÀNG HẢI BỘ MÔN: KHOA HOC̣ MÁ Y TÍNH KHOA: CÔNG NGHỆ THÔNG TIN BÀI GIẢNG LẬP TRÌNH HƢỚNG ĐỐI TƢỢNG VÀ C++ TÊN HỌC PHẦN : Lập trình hƣớng đối tƣợng và C++ MÃ HỌC PHẦN : 17209 TRÌNH ĐỘ ĐÀO TẠO : ĐẠI HỌC CHÍNH QUY DÙNG CHO SV NGÀNH : CÔNG NGHỆ THÔNG TIN HẢI PHÒNG - 2008
  2. Mô tả vắn tắt nội dung và khối lượng học phần Tên học phần: Lập trình hướng đối tượng và C++. Loại học phần : 2 Bộ môn phụ trách giảng dạy: Khoa học máy tính. Khoa phụ trách: CNTT Mã học phần: 17209 Tổng số TC: 4 TS tiết Lý thuyết Thực hành/ Xemina Tự học Bài tập lớn Đồ án môn học 75 45 30 0 0 0 Điều kiện tiên quyết: Sinh viên phải học và thi đạt các học phần sau mới được đăng ký học học phần này: Kỹ thuật lập trình Pascal, Kỹ thuật lập trình C. Mục tiêu của học phần: Cung cấp kiến thức của phương pháp lập trình hướng đối tượng và rèn luyện kỹ năng lập trình . Nội dung chủ yếu: - Những mở rộng của lập trình hướng đối tượng. - Đối tượng và lớp. - Đóng gói, thừa kế, đa hình. - Bản mẫu, thư viện STL Nội dung chi tiết: PHÂN PHỐI SỐ TIẾT TÊN CHƢƠNG MỤC TS LT Thực hành BT KT Chƣơng 1: Lâp̣ trình hướng đối tươṇ g và 3 3 ngôn ngữ C++ 1.1 Ưu điểm của lập trình hướng đối tượng 1.2 Giới thiệu ngôn ngữ C++ Chƣơng 2: Những khái niêṃ mở đầu 9 6 3 2.1 Cài đặt ngôn ngữ C++ 2.2 Cấu trúc một chương trình C++ 2.3 Kiểu dữ liệu cơ sở 2.4 Quy tắc sử dụng từ khóa, tên chuẩn, tên từ đặt. 2.5 Các chỉ thị gán, so sánh, điều kiện nếu thì Chƣơng 3: Con trỏ, tham chiếu và hàm 6 3 3 3.1 Khai báo hàm con, hàm chính. 3.2 Quy tắc đổi kiểu dữ liệu, kiểu trỏ. 3.3 Định nghĩa chồng hàm, tham số ngầm định 3.5 Tham chiểu. Chƣơng 4: Các dòng vào ra trong C++ 9 5 3 1 4.1 Đối tượng vào ra cout, cin 4.2 Đẩy dòng dữ liệu lên màn hình 4.3 Nhập dòng dữ liệu từ bàn phím 4.4 Định dạng dòng dữ liệu hiển thị 4.5 Vào ra với tệp Chƣơng 5: Đối tượng và Lớp 18 8 9 1 i
  3. 5.1 Định nghĩa đối tượng 5.2 Khai báo lớp 5.3 Hàm thiết lập, huỷ bỏ 5.4 Thành phần tĩnh, hàm bạn, lớp bạn 5.5 Định nghĩa chồng toán tử Chƣơng 6: Thừa kế 9 6 3 6.1 Lớp cơ sở, lớp dẫn xuất 6.2 Quy tắc thừa kế 6.3 Tương thích lớp cơ sở và lớp dẫn xuất 6.4 Đơn thừa kế, đa thừa kế Chƣơng 7: Ràng buộc động và Đa thể 8 5 3 7.1 Hàm ảo, ràng buộc tĩnh, động 7.2 Đa thể Chƣơng 8: Bản mẫu 13 6 6 1 8.1 Hàm bản mẫu 8.2 Ưu khuyết điểm của hàm bản mẫu 8.3 Lớp bản mẫu Nhiệm vụ của sinh viên: Lên lớp đầy đủ và chấp hành mọi quy định của Nhà trường. Tài liệu học tập: 1. Tên tác giả. Tên sách. Nhà xuất bản. Năm xuất bản. 2. Phạm Văn Ất. Kỹ thuật lập trình hướng đối tượng. NXB KHKT. 1998 3. Một số website liên quan. Hình thức và tiêu chuẩn đánh giá sinh viên: - Thi viết hoặc thi thực hành. - Sinh viên phải bảo đảm các điều kiện theo Quy chế của Nhà trường và của Bộ. Thang điểm : Thang điểm chữ A,B,C,D,F. Điểm đánh giá học phần: Z=0,3X+0,7Y. Bài giảng này là tài liệu chính thức và thống nhất của Bộ môn Khoa học Máy tính, Khoa Công nghệ Thông tin và được dùng để giảng dạy cho sinh viên. Ngày phê duyệt: / /20 Trƣởng Bộ môn: ThS. Nguyễn Hữu Tuân (ký và ghi rõ họ tên) ii
  4. MỤC LỤC CHƢƠNG I: LÂP̣ TRÌNH HƢỚNG ĐỐ I TƢỢNG VÀ NGÔN NGỮ C++ 1 1. Sự phát triển của các kỹ thuật lập trình 1 1.1 Lâp̣ trình không có cấu trúc (hay lâp̣ trình tuyến tính) 1 1.2 Lâp̣ trình thủ tuc̣ hay lâp̣ trình có cấu trúc 1 1.3 Lâp̣ trình module 3 1.4 Lâp̣ trình hướng đối tượng 4 2. Môṭ số khái niêṃ cơ bản của lập trình hướng đối tượng 5 2.1 Kiểu dữ liêụ trừu tượng ADT(Astract Data Type) 5 2.2 Đối tượng (Objects) và lớp (Classes) 5 2.3 Kế thừa (Inheritance) 6 2.4 Dynamic Binding (ràng buộc động) và Porlymorphism (đa xa ̣hoăc̣ đa thể) 6 3. Ngôn ngữ lâp̣ trình C++ và OOP. 7 3.1 Sự phát triển của các ngôn ngữ lâp̣ trình hướng đối tượng 7 3.2 Ngôn ngữ lâp̣ trình C++. 8 4. Bài tập 8 CHƢƠNG II: NHỮNG KHÁ I NIÊṂ MỞ ĐẦU 9 1. Chương trình đầu tiên 9 1.1 Quá trình biên dịch một chương trình C++ 9 1.2 Chương trình đầu tiên. 13 2. Biến, hằng và tầm hoaṭ đôṇ g của các biến 15 2.1 Cú pháp khai báo biến (variable declaration) 15 2.2 Tầm hoaṭ đôṇ g của các biến 16 2.3 Khai báo biến ngay trong cú pháp của các câu lêṇ h điều khiển 16 2.4 Các kiểu biến 17 2.5 Liên kết biến khi biên dic̣ h 18 2.6 Các hằng 18 3. Hàm trong C++ 19 4. Các cấu trúc điều khiển 20 4.1 Câu lêṇ h if-else 20 4.2 Vòng lặp không xác định while 20 4.3 Vòng lặp không xác định do – while 21 4.4 Vòng lặp xác định for 21 4.5 Các từ khóa break và continue 22 4.6 Câu lêṇ h lựa choṇ switch 22 4.7 Câu lêṇ h goto 23 4.8 Đệ qui 23 iii
  5. 5. Các kiểu dữ liêụ cơ bản của C++ 23 6. Môṭ số toán tử trong C++ 25 6.1 Toán tử gán (assignment operator) 25 6.2 Các toán tử toán học 25 6.3 Các toán tử quan hê ̣ 25 6.4 Các toán tử logic 26 6.5 Các toán tử bitwise 26 6.6 Các toán tử dịch 26 6.7 Các toán tử môṭ ngôi 26 6.8 Toán tử 3 ngôi 26 6.9 Toán tử dấu phẩy 27 6.10 Các lỗi thường găp̣ khi sử dụng các toán tử 27 6.11 Toán tử chuyển kiểu 27 6.12 Toán tử sizeof. 28 7. Các kiểu dữ liêụ người dùng điṇ h nghiã 28 8. Bài tập 31 CHƢƠNG III: CON TRỎ , THAM CHIẾ U VÀ HÀ M. 34 1. Hàm trong C++ 34 1.1 Nguyên mâũ và điṇ h nghĩa hàm 34 1.2 Hàm và các biến 34 1.3 Truyền tham số 34 1.4 Chồng hàm (overload) và tham số mặc định của hàm 35 1.5 Các vấn đề khác 36 2. Con trỏ, hàm và mảng 37 3. Hàm và xử lý xâu 38 4. Bài tập 38 CHƢƠNG IV: CÁC DÒNG VÀO RA TRONG C++ 40 1. Tổng quan về các luồng vào ra của C++ 40 2. Các luồng và các bộ đệm 41 3. Các đối tượng vào ra chuẩn 41 4. Điṇ h hướng laị (Redirection) 41 5. Nhâp̣ dữ liêụ với cin 42 6. Các hàm thành viên khác của cin 42 7. Kết xuất dữ liêụ với cout 45 8. Các dòng vào ra và hàm printf 46 9. Vào ra dữ liêụ với các file 46 10. File text và file nhi ̣phân 48 iv
  6. 11. Tìm kiếm trong các dòng vào ra 48 12. stringstream 49 13. Bài tập 52 CHƢƠNG V: ĐỐI TƢỢNG VÀ LỚP. 54 1. Trừu tượng dữ liêụ 54 2. Thế nào là môṭ đối tượng? 54 3. Các lớp và các đối tượng 55 4. Con trỏ và mảng các đối tượng 58 5. Khai báo các lớp với các file header 59 6. Kiểm soát viêc̣ truy câp̣ tới các biến và phương thức của lớp 61 7. Các hàm bạn và các lớp baṇ 62 8. Con trỏ this 66 9. Khởi taọ các đối tượng của lớp thông qua các hàm cấu tử 67 10. Hủy tử 72 11. Cấu tử copy 76 12. Đối tượng hằng và các hàm thành viên hằng 78 13. Các thành viên tĩnh của lớp 79 14. Sử dụng các đối tượng trong vai trò là tham số của hàm 80 15. Các đối tượng chồng nhau: Các lớp là thành viên của các lớp khác. 82 16. Chồng toán tử 84 17. Bài tập 91 CHƢƠNG VI: KẾ THỪA (INHERITANCE) 94 1. Sử dụng lại mã chương trình 94 2. Sử dụng lại mã chương trình trong OOP 94 3. Cú pháp kế thừa 94 4. Điṇ h nghĩa lại các thành viên của lớp cơ sở 96 5. Kiểm soát truy câp̣ 98 6. Các kiểu kế thừa 101 6.1 Kế thừa public 101 6.2. Kế thừa private 101 6.3 Kế thừa protected 102 7. Điṇ h nghiã laị các đăc̣ tả truy câp̣ 102 8. Các hàm không thể kế thừa 103 9. Các hàm cấu tử và kế thừa 103 10. Composition và Inheritance 105 11. Đa kế thừa 107 12. Lăp̣ laị lớp cơ sở trong đa kế thừa và lớp cơ sở ảo 108 v
  7. 13. Con trỏ và các đối tượng 109 14. Con trỏ và kế thừa 116 15. Bài tập 117 CHƢƠNG VII: RÀNG BUỘC ĐỘNG VÀ ĐA THỂ 118 1. Môṭ số đăc̣ điểm của ràng buôc̣ đôṇ g và đa thể 118 1.1 Ràng buộc động 118 1.2 Đa thể - Polymorphism 118 2. Các hàm thành viên bình thường được truy câp̣ qua các con trỏ 119 3. Các hàm thành viên ảo được truy câp̣ qua các con trỏ 120 4. Ràng buộc động 122 4.1 Ràng buộc động làm việc như thế nào 122 4.2 Không sử dụng con trỏ this 124 5. Danh sách liên kết các đối tượng và đa thể 124 6. Các lớp trừu tượng – abstract class 128 7. Các hàm ảo thực sự 129 7.1 Ví dụ 1 129 7.2 Ví dụ 2 131 8. Cấu tử ảo và hủy tử ảo 135 9. Hàm toán tử ảo 136 10. Bài tập 139 CHƢƠNG VIII: BẢN MẪU (TEMPLATE) 140 1. Các lớp bản mẫu 140 1.1 Các bản mẫu và thể nghiệm 141 1.2 Các thành phần tĩnh 146 1.3 Các lớp baṇ và lớp trợ giúp 147 1.4 Các tham số bản mẫu 149 1.5 Các lớp thuôc̣ tính 151 1.6 Các lớp chứa (bản mẫu) chuẩn 154 2. Các hàm bản mẫu 154 2.1 Các định nghĩa và thể nghiệm 154 2.2 Các hàm chuẩn chung – thư viêṇ thuâṭ toán 157 3. Bài tập 157 TÀI LIỆU THAM KHẢO 158 ĐỀ THI THAM KHẢO 159 vi
  8. CHƢƠNG I: LÂP̣ TRÌNH HƢỚ NG ĐỐ I TƢƠṆ G VÀ NGÔN NGƢ̃ C++ 1. Sƣ ̣ phá t triển củ a cá c ky ̃ thuâṭ lâp̣ triǹ h Phần này trình bày về môṭ số kỹ thuâṭ hay p hương pháp lâp̣ trình đươc̣ phát triển để giải quyết các vấn đề trong Tin học kể từ khi máy tính ra đời . Sư ̣ phát triển của các kỹ thuâṭ lâp̣ trình liên quan chăṭ che ̃ tớ i sư ̣ phát triển phần cứ ng của máy vi tính cũng như v iêc̣ ứ ng dụng máy tính vào giải quyết các vấn đề trong thực tế . Chúng ta có thể chia các phương pháp lập trình thành các kiểu sau: + Lâp̣ trình không có cấu trúc + Lâp̣ trình hướ ng thủ tuc̣ + Lâp̣ trình theo kiểu module hóa + Lâp̣ trình hướ ng đối tươṇ g Chúng ta sẽ lần lượt xem xét các kỹ thuật lập trình này. 1.1 Lâp̣ triǹ h không có cấ u trú c (hay lâp̣ triǹ h tuyến tính) Thông thườ ng moị ngườ i bắt đầu hoc̣ lâp̣ trình bằng cách viết các chương trình nhỏ và đơn giản chỉ chứa một “chương trình chính” . Ở đây một chương trình chính có nghĩa là môṭ tâp̣ các lêṇ h hoăc̣ câu lêṇ h làm viêc̣ vớ i các dữ liêụ toàn cuc̣ trong cả chương trình (các biến dùng trong chương trình là các biến toàn cục). Chúng ta có thể minh hoạ bằng hình vẽ sau đây: Lâp̣ triǹ h không có cấ u trú c. Chƣơng triǹ h chính thao tá c trƣc̣ tiếp trên cá c dƣ̃ liêụ toàn cuc̣ Môṭ số nhươc̣ điểm của lâp̣ trình không có cấu trúc: + Lâp̣ trình không có cấu trúc không có khả năng kiểm soát tính thấy đươc̣ của dữ liêụ . Mọi dữ liệu trong chương trình đều là biến toàn cục do đó có thể bị thay đổi bởi bất kỳ phần nào đó của chương trình. + Viêc̣ không kiểm soát đươc̣ tính thấy đươc̣ của dữ liêụ dâñ đến các khó khăn trong viêc̣ gỡ lỗi chương trình, đăc̣ biêṭ là các chương trình lớ n. + Kỹ thuật lập trình không có cấu trúc có rất nhiều bất lợi lớn khi chương trình đủ lớ n. Ví dụ nếu chúng ta cần thực hiện lại một đoạn câu lệnh trên một tập dữ liệu khác thì buôc̣ phải copy đoaṇ lêṇ h đó tớ i vi ̣trí trong chương trình mà chúng ta muốn thưc̣ hiêṇ . Điều này làm nảy sinh ý tưở ng trích ra các đo ạn lệnh thường xuyên cần thực hiện đó , đăṭ tên cho chúng và đưa ra môṭ kỹ thuâṭ cho phép goị và trả về các giá tri ̣từ các thủ tuc̣ này . 1.2 Lâp̣ triǹ h thủ tuc̣ hay lâp̣ triǹ h có cấ u trú c Vớ i lâp̣ trình thủ tuc̣ hay hướ ng thủ tục chúng ta có thể nhóm các câu lệnh thường xuyên thưc̣ hiêṇ trong chương trình chính laị môṭ chỗ và đăṭ tên đoaṇ câu lêṇ h đó thành 1
  9. môṭ thủ tuc̣ . Môṭ lờ i goị tớ i thủ tuc̣ se ̃ đươc̣ sử duṇ g để thưc̣ hiêṇ đoaṇ câu lêṇ h đó. Sau khi thủ tục thực hiện xong điều khiển trong chương trình được trả về ngay sau vị trí lời gọi tới thủ tục trong chương trình chính . Vớ i các cơ chế truyền tham số cho thủ tuc̣ chúng ta có các chương trình con . Môṭ chươn g trình chính bao gồm nhiều chương trình con và các chương trình đươc̣ viết mang tính cấu trúc cao hơn , đồng thờ i cũng ít lỗi hơn . Nếu môṭ chương trình con là đúng đắn thì kết quả thưc̣ hiêṇ trả về luôn đúng và chúng ta khôn g cần phải quan tâm tới các chi tiết bên trong của thủ tục . Còn nếu có lỗi chúng ta có thể thu hẹp phạm vi gỡ lỗi trong các chương trình con chưa được chứng minh là đúng đắn , đây đươc̣ xem như trừ u tươṇ g hàm và là nền tảng cho lâp̣ trình thủ tuc̣ . Môṭ chương trình chính vớ i lâp̣ trình thủ tuc̣ có thể đươc̣ xem là tâp̣ hơp̣ các lờ i goị thủ tục. Lâp̣ triǹ h thủ tuc̣ . Sau khi chƣơng triǹ h con thƣc̣ hiêṇ xong điều khiển đƣơc̣ trả về ngay sau vi ṭ rí lời goị tớ i chƣơng triǹ h con Chương trình chính có nhiêṃ vu ̣truyền các dữ liêụ cho các lờ i goị cu ̣thể , dữ liêụ đươc̣ xử lý cuc̣ bô ̣trong chương trình con sau đó các kết quả thưc̣ hiêṇ này đươc̣ trả về cho chương trình chính. Như vâỵ luồng dữ liêụ có thể đươc̣ minh hoạ như là môṭ đồ thi ̣phân cấp, môṭ cây: Lâp̣ triǹ h hƣớ ng thủ tuc̣ . Chƣơng triǹ h chính phố i hơp̣ cá c lời gọi tới các thủ tục với các dữ liệu thích hợp là các tham số Lâp̣ trình hướ ng thủ tuc̣ là môṭ kỹ thuâṭ lâp̣ trình có nhiều ưu điểm . Khái niệm chương trình con là môṭ ý tưở ng rất hay , nó cho phép một chương trình lớn có thể được chia thành nhiều chương trình con nhỏ hơn , đo đó dê ̃ viế t hơn và ít lỗi hơn . Để có thể sử dụng được các thủ tục chung hoặc một nhóm các thủ tục trong các chương trình khác , ngườ i ta đa ̃ phát minh ra môṭ kỹ thuâṭ lâp̣ trình mớ i , đó là kỹ thuâṭ lâp̣ trình theo kiểu module. 2
  10. 1.3 Lâp̣ triǹ h module Trong lâp̣ trình module các thủ tuc̣ có cùng môṭ chứ c năng chung se ̃ đươc̣ nhóm laị vớ i nhau taọ thành môṭ module riêng biêṭ . Môṭ chương trình se ̃ không chỉ bao gồm môṭ phần đơn lẻ. Nó được chia thành một vài phần nhỏ hơn tương tác vớ i nhau qua các lờ i goị thủ tục và tạo thành toàn bộ chương trình. Lâp̣ triǹ h module. Chƣơng triǹ h chính là sƣ ̣ kết hơp̣ giƣ̃a cá c lời goị tớ i cá c thủ tục trong các module riêng biệt với các dữ liệu thích hơp̣ Mỗi module có dữ liêụ riêng của nó . Điều này cho phép các module có thể kiểm soát các dữ liệu riêng của nó bằng các lời gọi tới các thủ tục trong module đó . Tuy nhiên mỗi module chỉ xuất hiêṇ nhiều nhất môṭ lần trong cả chương trình. Yếu điểm của lâp̣ trình thủ tuc̣ và lâp̣ trình module hóa: + Khi đô ̣phứ c tap̣ của chương trình 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ở nên rõ ràng rằng cấu trúc dữ liêụ sử dụng trong chương trình cũng quan trọng không kém các phép toán thực hiện trên chúng . Điều này càng lô ̣rõ khi kích thướ c chương trình tăng . Các kiểu dữ liệu được xử lý nhiều trong các thủ tuc̣ của môṭ chương trình có cấu trúc. Do đó khi thay đổi cài đăṭ của môṭ kiểu dữ liêụ se ̃ dâñ đến nhiều thay đổi trong các thủ tuc̣ sử duṇ g nó . + Môṭ nhươc̣ điểm nữa là khi cần dùng nhiều nhóm làm viêc̣ để xây dưṇ g môṭ chương trình chung. Trong lâp̣ trình có cấu trúc mỗi ngườ i se ̃ đươc̣ giao xây dưṇ g môṭ số thủ tục và kiểu dữ liệu . Những lâp̣ trình viên xử lý các thủ tuc̣ khác nhau nhưng laị có liên quan tớ i các kiểu dữ liêụ dùng chung nên nếu môṭ ngườ i thay đổi kiểu dữ liêụ thì se ̃ làm ảnh hưởng tới công việc của nhiều người khác , đăc̣ biêṭ là khi có sai sót trong viêc̣ liên lac̣ giữa các thành viên của nhóm. + Viêc̣ phát triển các phầm mềm mất nhi ều thời gian tập trung xây dựng lại các cấu trúc dữ liệu cơ bản . Khi xây dưṇ g môṭ chương trình mớ i trong lâp̣ trình có cấu trúc lâp̣ trình viên thường phải xây dựng lại các cấu trúc dữ liệu cơ bản cho phù hợp với bài toán và điều này đôi khi rất mất thờ i gian. 3
  11. 1.4 Lâp̣ triǹ h hƣớ ng đố i tƣơṇ g Trong lâp̣ trình hướ ng đối tươṇ g trong mỗi chương trình chúng ta có môṭ số các đối tươṇ g (object) có thể tương tác với nhau , thuôc̣ các lớ p (class) khác nhau, mỗi đối tươṇ g tư ̣ quản lý lấy các dữ liêụ của riêng chúng. Lâp̣ trình hướ ng đối tươṇ g. Các đối tượng tương tác với nhau bằng cách gửi các thông điệp. Chương trình chính se ̃ bao gồm môṭ số đối tươṇ g là thể hiêṇ (instance) của các lớp , các đối tượng này tương tác với nhau thực hiện các chức năng của chương trình . Các lớp trong lâp̣ trình hướ ng đối tươṇ g có thể xem như là môṭ sư ̣ trừ u tươṇ g ở mứ c cao hơn của các cấu trúc (struct hay record) hay kiểu dữ liêụ do ngườ i dùng điṇ h nghiã trong các ngôn ngữ lâp̣ trình có cấu trúc vớ i sư ̣ tích hơp̣ cả các toán tử và dữ liêụ trên các kiểu đó . Các ưu điểm của lập trình hướng đối tượng: + Lâp̣ trình hướ ng đối tươṇ g ra đờ i đa ̃ giải quyết đươc̣ nhiều nhươc̣ điểm tồn taị trong lâp̣ trình có cấu trúc . Trong lâp̣ trình OOP có ít lỗi hơn và viêc̣ gỡ lỗi cũng đơn giản hơn, đồng thờ i lâp̣ trình theo nhóm có thể thưc̣ hiêṇ rất hiêụ quả . Ít lỗi là môṭ trong các ưu điểm chính của OOP vì theo thống kê thì viêc̣ bảo trì hê ̣thống phần mềm sau khi giao cho ngườ i dùng chiếm tớ i 70% giá thành phần mềm. + Viêc̣ thay đổi các cài đăṭ chi tiết bên dướ i trong lâp̣ trình OOP không làm ảnh hương tớ i các phần khác của chương trình do đó viêc̣ mở rôṇ g qui mô của môṭ chương trình dễ dàng hơn, đồng thờ i làm giảm thờ i gian cần thiết để phát triển phần mềm. + Vớ i khái niêṃ kế thừ a các lâp̣ trình viên có thể xây dưṇ g các chương trình từ các phần mềm sẵn có. + OOP có tính khả chuyển cao . Môṭ chương trình viết trên môṭ hê ̣thống nền (chẳng hạn Windows) có thể chạy trên nhiều hệ thống nền khác nhau (chẳng haṇ Linux, Unix ). + OOP có hiêụ quả cao . Thưc̣ tế cho thấy các hê ̣thống đươc̣ xây dưṇ g bằng OOP có hiêụ năng cao. 4
  12. 2. Môṭ số khá i niêṃ cơ bản củ a lâp̣ triǹ h hƣớ ng đố i tƣơṇ g 2.1 Kiểu dƣ̃ liêụ trƣ̀ u tƣơṇ g ADT(Astract Data Type) Môṭ số ngườ i điṇ h nghiã OOP là lâp̣ trình vớ i các kiểu dữ liêụ trừ u tươṇ g và các mối quan hê ̣giữa chúng . Trong phần này chúng ta se ̃ xem xét các kiểu dữ liêụ trừ u tươṇ g như là một khái niệm cơ bản của OOP và sử dụng một số ví dụ để minh họa. Điṇ h nghiã về kiểu dữ liêụ trừ u tươṇ g : Môṭ kiểu dữ liêụ trừ u tươṇ g là môṭ mô hình toán học của các đối tượng dữ liệu tạo thành một kiểu dữ liệu và các toán tử (phép toán) thao tác trên các đối tươṇ g đó. Chú ý là trong định nghĩa này các toán tử thao tác trên các đối tươṇ g dữ liêụ gắn liền vớ i các đối tươṇ g taọ thành môṭ kiểu dữ liêụ trừ u tươṇ g . Đặc tả về môṭ kiểu dữ liêụ trừ u tươṇ g không có bất kỳ môṭ chi tiế t cu ̣thể nào về cài đăṭ bên trong của kiểu dữ liệu. Viêc̣ cài đăṭ môṭ kiểu dữ liêụ trừ u tươṇ g đòi hỏi môṭ quá trình chuyển đổi từ đăc̣ tả của nó sang môṭ cài đăṭ cu ̣thể trên môṭ ngôn ngữ lâp̣ trình cu ̣thể . Điều này cho phép chúng ta phân biệt các ADT với các thuật ngữ kiểu dữ liệu (data type) và cấu trúc dữ liêụ (data structure). Thuâṭ ngữ kiểu dữ liêụ đề câp̣ tớ i môṭ cài đăṭ cu ̣thể (có thể là kiểu built in hoăc̣ do ngườ i dùng điṇ h nghĩa) của một mô hình toán học được đặc tả bởi một ADT. Cấu trúc dữ liêụ đề câp̣ tớ i môṭ tâp̣ các biến có cùng kiểu đươc̣ gắn kết vớ i nhau theo môṭ cách thứ c xác điṇ h nào đó. Ví dụ về kiểu dữ liệu trừu tượng: Số nguyên. Kiểu dữ liêụ trừ u tươṇ g số nguyên: ADT Integer: Dƣ̃ liêụ : môṭ tâp̣ các chữ số và môṭ dấu tiền tố là + hoăc̣ -. Chúng ta ký hiệu cả số là N. Các toán tử: constructor: khở i taọ môṭ số nguyên sub(k): trả về hiệu N – k. add(k): trả về tổng N + k. End 2.2 Đối tƣợng (Objects) và lớp (Classes) Trong môṭ chương trình hướ ng đối tươṇ g chúng ta có các đối tươṇ g . Các đối tượng này là đại diện cho các đối tượng thực trong thực tế . Có thể coi khái niêṃ đối tươṇ g trong OOP chính là các kiểu dữ liêụ trong các ngôn ngữ lâp̣ trình có cấu trúc . Mỗi môṭ đối tươṇ g có các dữ liệu riêng của nó và được gọi là các member variable hoặc là các data member . Các toán tử thao tác trên các dữ liệu này được gọi là các member function . Mỗi môṭ đối tươṇ g là thể hiêṇ (instance) của một lớp. Như vâỵ lớ p là đaị diêṇ cho các đối tươṇ g có các member function giống nhau và các data member cùng kiểu . Lớ p là một sự trừu tượng hóa của khái niệm đối tượng. Tuy nhiên lớ p không phải là môṭ ADT , nó là một cài đặt của một đăc̣ tả ADT. Các đối tượng của cùng một lớp có thể chia sẻ các dữ liệu dùng chung , dữ liêụ kiểu này được gọi là class variable. 5
  13. 2.3 Kế thƣ̀ a (Inheritance) Khái niệm kế thừa này sinh từ nhu cầu sử dụng lại các thành phần phần mềm để phát triển các phần mềm mớ i hoăc̣ mở rôṇ g chứ c năng của phần mềm hiêṇ taị . Kế thừ a là mô ̣t cơ chế cho phép các đối tươṇ g của môṭ lớ p có thể truy câp̣ tớ i các member variable và function của môṭ lớ p đa ̃ đươc̣ xây dưṇ g trướ c đó mà không cần xây dưṇ g laị các thành phần đó. Điều này cho phép chúng ta có thể taọ r a các lớ p mớ i là môṭ mở rôṇ g hoăc̣ cá biêṭ hóa của một lớp sẵn có . Lớ p mớ i (gọi là derived class ) kế thừ a từ lớ p cũ (gọi là lớp cơ sở base class). Các ngôn ngữ lập trình hướng đối tượng có thể hỗ trợ khái niệm đa kế thừ a cho phép một lớp có thể kế thừa từ nhiều lớp cơ sở . Lớ p kế thừ a derived class có thể có thêm các data member mới hoặc các member function mới . Thêm vào đó lớ p kế thừ a có thể tiến hành định nghĩa lại một hàm của lớ p cơ sở và trong trườ ng hơp̣ này ngườ i ta nói rằng lớp kế thừ a đa ̃ overload hàm thành viên của lớ p cơ sở . 2.4 Dynamic Binding (ràng buộc động) và Porlymorphism (đa xa ̣hoăc̣ đa thể) Chúng ta lấy một ví dụ để minh hoạ cho hai k hái niệm này. Giả sử chúng ta có một lớ p cơ sở là Shape , hai lớ p kế thừ a từ lớ p Shape là Circle và Rectange . Lớ p Shape là môṭ lớ p trừ u tươṇ g có môṭ member function trừ u tươṇ g là draw (). Hai lớ p Circle và Rectange thưc̣ hiêṇ overload laị hàm draw của lớ p Shape vớ i các chi tiết cài đăṭ khác nhau chẳng haṇ vớ i lớ p Circle hàm draw se ̃ ve ̃ môṭ vòng tròn còn vớ i lớ p Rectange thì se ̃ ve ̃ môṭ hình chữ nhâṭ. Và chúng ta có một đoạn chương trình chính hợp lệ như sau: int main() { Shape shape_list[4]; int choose; int i; for(i=0;i > choose; if(choose==0) { shape_list[i] = new Circle(); }else{ shape_list[i] = new Rectange(); } } for(i=0;i<4;i++) { 6
  14. shape_list[i]->draw(); } } Khi biên dic̣ h chương trình này thành ma ̃ thưc̣ hiêṇ (file .exe) trình biên dịch không thể xác điṇ h đươc̣ trong mảng shape _list thì phần tử nào là Circle phần tử nào là Rec tange và do đó không thể xác định được phiên bản nào của hàm draw sẽ được gọi thực hiện . Viêc̣ gọi tới phiên bản nào của hàm draw để thực hiện sẽ được quyết định tại thời điểm thực hiêṇ chương trình, sau khi đa ̃ biên dic̣ h và điều này được gọi là dynamic binding hoặc late binding. Ngươc̣ laị nếu viêc̣ xác điṇ h phiên bản nào se ̃ đươc̣ goị thưc̣ hiêṇ tương ứ ng với dữ liêụ gắn vớ i nó đươc̣ quyết điṇ h ngay trong khi biên dic̣ h thì ngườ i ta goị đó là static binding. Ví dụ này cũng cung cấp cho chúng ta một minh họa về khả năng đa thể (polymorphism). Khái niệm đa thể được dùng để chỉ khả năng của một thông điệp có thể đươc̣ gử i tớ i cho các đối tươṇ g của nhiều lớ p khác n hau taị thờ i điểm thưc̣ hiêṇ chương trình. Chúng ta thấy rõ lời gọi tới hàm draw sẽ được gửi tới cho các đối tượng của hai lớp Circle và Rectange taị thờ i điểm chương trình đươc̣ thưc̣ hiêṇ . Ngoài các khái niệm cơ bản trên OOP còn có thêm môṭ số khái niêṃ khác chẳng haṇ như name space và exception handling nhưng không phải là các khái niêṃ bản chất. 3. Ngôn ngƣ̃ lâp̣ triǹ h C++ và OOP. Giống như bất kỳ môṭ ngôn ngữ nào của con ngườ i , môṭ ngôn ngữ l ập trình là phương tiêṇ để diêñ tả các khái niêṃ , ý tưởng. Viêc̣ phát triển các chương trình hay phần mềm là quá trình mô hình hóa các traṇ g thái tư ̣ nhiên của thế giớ i thưc̣ và xây dưṇ g các chương trình dưạ trên các mô hình đó. Các chương trình thực hiện chức năng mô tả phương pháp cài đặt của mô hình. Các thế hệ ngôn ngữ lập trình : Có thể phân chia các thế hệ ngôn ngữ lập trình thành 4 thế hê:̣ 1: vào năm 1954 – 1958 (Fortran I) vớ i đăc̣ điểm là các biểu thứ c toán hoc̣ 2: vào năm 1959 – 1961 (Fortran II, Cobol) vớ i các thủ tuc̣ 3: vào những năm 1962 – 1970 (Pascal, Simula) vớ i đăc̣ trưng là các khối, các lớp 4: đang phát triển chưa có dâñ chứ ng thưc̣ tế. Các ngôn ngữ này ngày càng cách xa ngôn ngữ máy và các trình biên dịch của chúng ngày càng phải làm việc nhiều hơn. 3.1 Sƣ ̣ phá t triển củ a cá c ngôn ngƣ̃ lâp̣ triǹ h hƣớ ng đố i tƣơṇ g 1967 Simula 1970 to 1983 Smalltalk 7
  15. 1979 Common LISP Object System 1980 Stroustrup starts on C++ 1981 Byte Smalltalk issue 1983 Objective C 1986 C++ 1987 Actor, Eiffel 1991 C++ release 3.0 1995 Java 1983 to 1989 Language books with OO concepts 1989 to 1992 Object-oriented design books 1992 to present Object-oriented methodology books Các ngôn ngữ lập trình khác Java Self Python Perl Prograph Modula 3 Oberon Smalltalk Venders ParcPlace, Digitalk, Quasar Prolog++ Ada 9X Object Pascal (Delphi) Object X, X = fortran, cobal, etc. C#. Như vâỵ là có rất nhiều ngôn ngữ lâp̣ trình hướ ng đối tươṇ g đa ̃ ra đờ i và chiếm ưu thế trong số chúng là C ++ và Java. Mỗi ngôn ngữ đều có đăc̣ điểm riêng của nó và thích hơp̣ vớ i các liñ h vưc̣ khác nhau nhưng có le ̃ C ++ là ngôn ngữ cài đăṭ nhiều đăc̣ điểm của OOP nhất. 3.2 Ngôn ngƣ̃ lâp̣ triǹ h C++. C++ là một ngôn ngữ lập trình hướng đối tượng được Bjarne Stroustrup (AT & T Bell Lab) (giải thưởng ACM Grace Murray Hopper năm 1994) phát triển từ ngôn ngữ C. C++ kế thừ a cú pháp và môṭ số đăc̣ điểm ưu viêṭ của C : ví dụ như xử lý con trỏ , thư viêṇ các hàm phong phú đa dạng , tính khả chuyển cao , chương trình chaỵ nhanh . Tuy nhiên về bản chất thì C++ khác hoàn toàn so với C, điều này là do C++ là một ngôn ngữ lập trình hướ ng đối tươṇ g. 4. Bài tập Bài tập 1: Download bộ công cụ DevCpp từ Internet và cài đặt trên máy tính của mình, viết chương trình đầu tiên, thực hiện biên dịch và chạy thử. Bài tập 2: Download bộ công cụ Visual Studio từ website của Microsoft và cài đặt trên máy tính của mình, viết chương trình, thực hiện biên dịch và chạy thử. 8
  16. CHƢƠNG II: NHƢ̃ NG KHÁ I NIÊṂ MỞ ĐẦ U 1. Chƣơng triǹ h đầu tiên 1.1 Quá trình biên dic̣ h môṭ chƣơng triǹ h C++ Tất cả các ngôn ngữ trên máy tính đều đươc̣ dic̣ h từ môṭ daṇ g nào đó mà con người có thể hiểu được một cách dễ dàng (các file mã nguồn được viết bằng một ngôn ngữ bậc cao) sang daṇ g có th ể thực hiện được trên máy tính (các lệnh dưới dạng ngôn ngữ máy ). Các chương trình thực hiện quá trình này chia thành hai dạng được gọi tên là các trình thông dic̣ h (interpreter) và các trình biên dịch (compiler). Trình thông dịch: Môṭ trình thông dic̣ h se ̃ dic̣ h ma ̃ nguồn thành các hành đôṇ g (activity), các hành động này có thể bao gồm một nhóm các lệnh máy và tiến hành thực hiêṇ ngay lâp̣ tứ c các hành đôṇ g này . Ví dụ như BASIC là một ngôn ngữ điển hình cho các ngôn ngữ thông dic̣ h . BASIC cổ điển thông dic̣ h từ ng dòng lêṇ h thưc̣ hiêṇ và sau đó quên ngay lâp̣ tứ c dòng lêṇ h vừ a thông dic̣ h . Điều này làm cho quá trình thưc̣ hiêṇ cả môṭ chương trình châṃ vì bô ̣thông di c̣ h phải tiến hành dic̣ h laị các đoaṇ ma ̃ trùng lăp̣ . BASIC ngày nay đã thêm vào qúa trình biên dịch để cải thiện tốc độ của chương trình . Các bộ thông dic̣ h hiêṇ đaị chẳng haṇ như Python , tiến hành dic̣ h toàn bô ̣chương trình qua môṭ ngôn ngữ trung gian sau đó thưc̣ hiêṇ bằng môṭ bô ̣thông dic̣ h nhanh hơn rất nhiều . Các ngôn ngữ làm viêc̣ theo kiểu thông dic̣ h thườ ng có môṭ số haṇ chế nhất điṇ h khi xây dưṇ g các dự án lớn (Có lẽ chỉ duy nhất Python là một ngoại lệ ). Bô ̣thông dic̣ h cần phải luôn đươc̣ lưu trong bô ̣nhớ để thưc̣ hiêṇ các ma ̃ chương trình , và thậm chí ngay cả bộ thông dịch có tốc độ nhanh nhất cũng không thể cải thiện được hoàn toàn các hạn chế tốc độ .Hầu hết các bô ̣thông dic̣ h đều yêu cầu toàn bô ̣ma ̃ nguồn cần phải đươc̣ thông dic̣ h môṭ lần duy nhất. Điều này không những dâñ đến các haṇ chế về kích thướ c của chương trình mà còn tạo ra các lỗi rất khó gỡ rối nếu như n gôn ngữ không cung cấp các công cu ̣hiêụ quả để xác điṇ h hiêụ ứ ng của các đoaṇ ma ̃ khác nhau. Trình biên dịch : Môṭ trình biên dic̣ h dic̣ h ma ̃ nguồn trưc̣ tiếp thành ngôn ngữ assembly hoăc̣ các lêṇ h máy. Kết quả cuối cùng là môṭ file duy nhất hoăc̣ các file chứ a các mã máy. Đây là môṭ quá trình phứ c tap̣ và đòi hỏi môṭ vài bướ c . Quá trình chuyển đổi từ mã chương trình ban đầu thành mã thực hiện là tương đối dài đối với một trình biên dịch . Tùy thuộc vào sự nhạy cảm của người viết trình biên dịch , các chương trình sinh ra bởi môṭ trình biên dic̣ h có xu hướ ng đòi hỏi ít bô ̣nhớ hơn khi thưc̣ hiêṇ , và chúng chạy nhanh hơn rất nhiều. Măc̣ dù kích thướ c và tốc đô ̣ thườ ng là các lý do hàng đầu cho viêc̣ sử duṇ g môṭ trình biên dic̣ h , trong rất nhiều trườ ng hơp̣ đây không phải là các lý do quan troṇ g nhất. Môṭ vài ngôn ngữ (chẳng haṇ như C ) đươc̣ thiết kế để các phần tách biêṭ của môṭ chương trình có thể đươc̣ biên dic̣ h đôc̣ lâp̣ hoàn toàn vớ i nhau . Các phần này sau đó thậm chí có thể kết hợp thành một chương trình thực hiện cuối cùng duy nhất bởi một công cụ có tên là trình liên kết . Quá trình này gọi là separate compilation (biên dic̣ h đôc̣ lâp̣ ). Biên dịch độc lập có rất nhiều điểm lợi . Môṭ chương trình nếu dic̣ h ngay lâp̣ tứ c toàn bô ̣se ̃ vươṭ quá các giới hạn của trình biên dịch hay môi trường biên dịch có thể được biên d ịch theo từ ng phần. Các chương trình có thể được xây dựng và kiểm thử từng phần một . Nếu moṭ phần nào đó đa ̃ làm viêc̣ đúng đắn nó có thể đươc̣ lưu laị như là môṭ khối đa ̃ hoàn thành . Tâp̣ các phần đa ̃ làm viêc̣ và đươc̣ kiểm thử có thể kết hơp̣ laị vớ i nhau taọ thành các thư viêṇ để các lâp̣ trình viên khác có thể sử duṇ g . Các đặc điểm này hỗ trợ cho việc tạo ra các chương trình lớ n. Các đặc điểm gỡ lỗi của trình biên dịch đã cải t iến môṭ cách đáng kể qua 9
  17. thờ i gian. Các trình biên dịch đầu tiên chỉ sinh ra mã máy , và lập trình viên phải chèn các câu lêṇ h in vào để xem thưc̣ sư ̣ chương trình đang làm gì . Điều này không phải lúc nào cũng hiệu quả. Các trình biên dịch hiện đại có thể chèn các thông tin về mã nguồn vào mã thưc̣ hiêṇ của chương trình. Thông tin này se ̃ đươc̣ sử duṇ g bở i các bô ̣gỡ lỗi cấp đô ̣nguồn đầy năng lưc̣ để chỉ ra chính xác điều gì đang diêñ ra tro ng môṭ chương trình bằng cách theo dấu (tracing) quá trình thực hiện của nó qua toàn bộ mã nguồn . Môṭ vài trình biên dịch giải quyết vấn đề tốc độ biên dịch bằng cách thực hiện quá trình biên dịch trong bộ nhớ (in-memory compilation). Các trình biên dịch theo kiểu này lưu trình biên dịch trong bô ̣nhớ RAM . Đối với các chương trình nhỏ , quá trình này có thể xem như là một trình thông dic̣ h. Quá trình biên dịch Để lâp̣ trình bằng C và C ++ chúng ta cần phải hiểu các bướ c và các công cu ̣trong quá trình biên dịch . Môṭ vài ngôn ngữ (đăc̣ biêṭ là C và C ++) bắt đầu thưc̣ hiêṇ quá trình biên dic̣ h bằng cách chaỵ môṭ bô ̣tiền xử lý đối vớ i ma ̃ nguồn . Bô ̣tiền xử lý là một chương trình đơn giản thay thế các mẫu trong mã nguồn bằng các mẫu khác mà các lập trình viên đa ̃ điṇ h nghiã (sử duṇ g các chỉ thi ̣tiền xử lý : preprocessor directives). Các chỉ thị tiền xử lý được sử dụng để tiết kiệ m viêc̣ gõ các đoaṇ chương trình thườ ng xuyên sử duṇ g và tăng khả năng dễ đọc cho mã nguồn . Tuy nhiên các chỉ thi ̣tiền xử lý này đôi khi cũng gây ra những lỗi rất tinh vi và khó phát hiêṇ . Mã sinh ra bởi bộ tiền xử lý này thườ ng đươc̣ ghi lên môṭ file taṃ . Các trình biên dịch thường thực hiện công việc của nó theo hai pha . Đầu tiên là phân tích mã tiền xử lý. Bô ̣biên dic̣ h chia ma ̃ tiền xử lý thành các đơn vi ̣nhỏ và tổ chứ c chúng thành một cấu trúc goị là cây . Ví dụ như trong biểu thức : “A+B” các phần tử “A” , “+”, “B” se ̃ đươc̣ lưu trên nút của cây phân tích . Môṭ bô ̣tớ i ưu hóa toàn cuc̣ (global optimizer) đôi khi cũng đươc̣ sử duṇ g để taọ ra ma ̃ chương trình nhỏ hơn, nhanh hơn. Trong pha thứ hai , bô ̣sinh ma ̃ duyêṭ qua cây phân tích và sinh ra hoăc̣ là ma ̃ assemble hoăc̣ ma ̃ máy cho các nút của cây . Nếu như bô ̣sinh ma ̃ taọ ra ma ̃ assembly , thì sau đó chương trình dic̣ h ma ̃ assembler se ̃ thưc̣ hiêṇ côn g viêc̣ tiếp theo . Kết quả của hai trườ ng hơp̣ trên đều là môṭ module object (môṭ file thườ ng có đuôi là .o hoăc̣ .obj). Sau đó môṭ bô ̣tối ưu hoá nhỏ (peep-hole) sẽ được sử dụng để loại bỏ các đoạn chứa các câu lệnh assembly thừ a. Viêc̣ sử duṇ g từ “object” để mô tả các đoaṇ ma ̃ máy là môṭ thưc̣ tế không đúng lắm. Từ này đa ̃ đươc̣ dùng trướ c cả khi lâp̣ trình hướ ng đối tươṇ g ra đờ i . Từ “object” đươc̣ sử duṇ g có ý nghiã như là từ “goal” khi nói về viêc̣ biên dic̣ h, trong khi đó trong lâp̣ trình hướng đối tượng nó lại có nghĩa là “a thing with boundaries” . Trình liên kết kết hợp môṭ danh sách các module object thành môṭ chương trình thưc̣ hiêṇ có thể nap̣ vào bô ̣nhớ và th ực hiện bởi hệ điều hành . Khi môṭ hàm trong môṭ module object taọ ra môṭ tham chiếu tớ i môṭ hàm hoăc̣ môṭ biến trong môṭ module object khác , trình liên kết sẽ sắp xếp lại các tham chiếu này ; điều này đảm bảo rằng tất cả c ác hàm và dữ liệu external được sử dụng trong quá trình biên dịch là đều tồn tại . Trình liên kết cũng thêm vào các module object đăc̣ biêṭ để thưc̣ hiêṇ các hành đôṇ g khở i đôṇ g . Trình liên kết có thể tìm kiếm trên các file đăc̣ biêṭ goị là các thư viêṇ để sắp xếp laị tất cả các tham chiếu tớ i chúng . Mỗi thư viêṇ chứ a môṭ tâp̣ các module object trong môṭ file. Môṭ thư viêṇ đươc̣ taọ ra và bảo trì bởi môṭ lâp̣ trình viên có tên là librarian. Kiểm tra kiểu tiñ h 10
  18. Trình biên dịch thực hiện kiểm tra kiểu trong pha đầu tiên của quá trình biên dịch . Quá trình kiểm tra này thực hiện kiểm thử việc sử dụng các tham số của các hàm và ngăn chăṇ rất nhiều lỗi lâp̣ trình k hác nhau. Vì quá trình kiểm tra kiểu được thực hiện trong qúa trình biên dịch chứ không phải trong quá trình chương trình thực hiện nên nó được gọi là kiểm tra kiểu tiñ h. Môṭ vài ngôn ngữ lâp̣ trình hướ ng đối tươṇ g (Java chẳng hạn) thưc̣ hiêṇ kiểm tra kiểu taị thờ i điểm chương trình chaỵ (dynamic type checking). Nếu kết hơp̣ cả viêc̣ kiểm tra kiểu tiñ h và đôṇ g thì se ̃ hiêụ quả hơn nhưng kiểm tra kiểu đôṇ g cũng làm cho chương trình thưc̣ hiêṇ bi ̣ả nh hưở ng đôi chút . C++ sử duṇ g kiểm tra kiểu tiñ h . Kiểm tra kiểu tiñ h báo cho lâp̣ trình viên về các lỗi về sử duṇ g sai kiểu dữ liêụ trong quá trình biên dịch, và do đó tối ưu hóa tốc độ thực hiện chương trình . Khi hoc̣ C++ chúng ta sẽ thấy hầu hết các quyết điṇ h thiết kế của ngôn ngữ đều tâp̣ trung vào củng cố các đăc̣ điểm : tốc đô ̣ nhanh, hướ ng đối tươṇ g, các đặc điểm mà đã làm cho ngôn ngữ C trở nên nổi tiếng . Chúng ta có thể không dùn g tùy choṇ kiểm tra kiểu tiñ h của C ++ hoăc̣ cũng có thể thưc̣ hiêṇ viêc̣ kiểm tra kiểu đôṇ g - chỉ cần viết thêm mã. Các công cụ cho việc biên dịch độc lập Viêc̣ biên dic̣ h đôc̣ lâp̣ rất cần thiết nhất là đối vớ i các dư ̣ án lớ n. Trong ngôn ngữ C và C++, môṭ lâp̣ trình viên có thể taọ ra các đoaṇ chương trình nhỏ dê ̃ quản lý và đươc̣ kiểm thử đôc̣ lâp̣ . Công cu ̣cơ bản để chia môṭ chương trình thành các phần nhỏ là khả năng taọ ra các thay thế đượ c đăṭ tên hay là các chương trình con . Trong C và C ++ môṭ chương trình con đươc̣ goị là môṭ hàm , và các hàm là các đoạn mã có thể được thay thế trong các file khác nhau , cho phép thưc̣ hiêṇ quá trình biên dic̣ h đôc̣ lâp̣ . Nói môṭ cách khác các hàm là các đơn vị nguyên tử của mã nguồn , vì chúng ta không thể đặt các phần khác nhau của hàm trong các file khác nhau nên nội dung của một hàm cần phải được đặt hoàn toàn trong một file (măc̣ dù các file có thể chứ a nhiều hơn 1 hàm). Khi chúng ta goị đến môṭ hàm, chúng ta thường truyền cho nó một vài tham số , đó là các giá trị mà chúng ta muốn hàm làm việc với khi nó thực hiện . Khi hàm thưc̣ hiêṇ xong chúng ta thường nhâṇ đươc̣ môṭ giá tri ̣trả về, môṭ gía tri ̣mà hàm trả laị như là môṭ kết quả . Cũng có thể viết các hàm không nhận các tham số và không trả về bất kỳ giá trị nào . Để tạo ra một chương trình với nhiều file , các hàm trong môṭ file phải truy câp̣ tớ i các hàm và dữ liêụ trong các file khác . Khi biên dic̣ h môṭ file, trình biên dịch C hoặc C ++ phải biết về các hàm và dữ liệu trong các file khác đặc biệt là tên và cách dùng chúng . Trình biên dịch đảm bảo các hàm và dữ liêụ đươc̣ sử duṇ g đúng đắn . Qúa trình báo cho trình biên dịch tên và nguyên mẫu của các hàm và dữ liệu bên ngoài được gọi là khai báo (declaration). Khi chúng ta đã khai báo một hàm hoặc biế n trình biên dic̣ h se ̃ biết cách thứ c kiểm tra để đảm bảo các hàm và dữ liệu này được sử dụng đúng đắn. Including cá c file Header Hầu hết các thư viêṇ đều chứ a môṭ số lươṇ g đáng kể các hàm và biến . Để tiết kiêṃ công sứ c và đảm bảo sư ̣ nhất quán khi khai báo ngoài các phần tử này , C và C ++ đa ̃ sử dụng một loại file được gọi là file header . Mỗi file header là môṭ file chứ a các khai báo ngoài cho 1 thư viêṇ ; theo qui ướ c các file này có phầ n mở rôṇ g là .h, nhưng chúng ta cũng có thể dùng các đuôi file khác cho chúng chẳng hạn như .hpp hoăc̣ .hxx. Lâp̣ trình viên taọ ra các file thư viêṇ se ̃ cung cấp các header file . Để khai báo các hàm và các biến bên ngoài thư viêṇ ngườ i dùng đơn giản chỉ cần thưc̣ hiêṇ include file header đó . Để include môṭ file header chúng ta sử duṇ g chỉ thi ̣tiền xử lý #include. Chỉ thị này sẽ báo cho bộ xử lý mở file 11
  19. header có tên tương ứ ng và chèn nôị dung của file đó vào chỗ mà chỉ thi ̣ #include đươc̣ sử dụng. Tên file sử duṇ g sau chỉ thi ̣ #include có thể nằm giữa hai dấu hoăc̣ giữa hai dấu “. Ví dụ: #include Nếu chúng ta sử duṇ g chỉ thi ̣include theo cách trên thì b ộ tiền xử lý sẽ tìm file header theo cách đăc̣ thù đối vớ i cài đăṭ của chúng ta , nhưng thườ ng thì se ̃ có môṭ vài đườ ng dâñ mà chúng ta chỉ điṇ h cu ̣thể trong biến môi trườ ng của trình biên dic̣ h hoăc̣ trên dòng lệnh để sử dụng cho việc tìm các file header . Cơ chế thiết lâp̣ các đườ ng dâñ này phu ̣ thuôc̣ vào trình biên dic̣ h và môi trườ ng mà chúng ta làm viêc̣ . Ví dụ: #include “header.h” Chỉ thị tiền xử lý như trên thường có ý nghĩa là báo cho bô ̣tiền xử lý tìm file tương ứng trong thư mục hiện tại trước nếu không thấy thì sẽ tìm giống như trong trường hợp tên file include đươc̣ đăṭ giữa hai dấu . Nói chung thì đối với các file include chuẩn hoặc đươc̣ sử duṇ g nhiều chúng ta nên đăc̣ nó trong thư muc̣ măc̣ điṇ h là include dướ i thư muc̣ cài đặt trình biên dịch và dùng chỉ thị theo kiểu <>, còn đối với các file đặc thù với ứng dụng cụ thể thì dùng kiểu tên file đặt giữa hai dấu “” . Trong quá trình phát triển của C ++ các nhà cung cấp các trình biên dịch có các qui ước đặt tên khác nhau và các hệ điều hành lại có các hạn chế tên khác nhau đặc biệt là độ dài của tên file . Các vấn đề này gây ra các vấn đề về tính khả chuyển của chương trình . Để khắc phuc̣ vấn đề này ngườ i ta đa ̃ sử duṇ g môṭ điṇ h daṇ g chuẩn cho phép các tên file header có thể dài hơn 8 ký tự và bỏ đi phần tên mở rôṇ g. Để phân biêṭ môṭ chương trình C và C ++ đôi khi ngườ i ta còn dùng cách thêm môṭ ký tư ̣ “c” vào trướ c tên của các file header , chi tiết này cũng đươc̣ chấp nhâṇ đối với C và C++. Quá trình liên kết Trình liên kết tập hợp các module object (thườ ng là các file có phần mở rộng là .o hoăc̣ .obj), đươc̣ sinh ra bở i trình biên dic̣ h , thành một chương trình có thể thực hiện được và hệ điều hành có thể nạp vào bộ nhớ và chạy . Đây là pha cuối cùng trong quá trình biên dịch. Các đặc điểm của các trình liên kết thay đổi phu ̣thuôc̣ vào các hê ̣thống khác nhau . Nói chung chúng ta chỉ cần chỉ rõ cho trình liên kết biết tên của các module object và các thư viêṇ mà chúng ta muốn liên kết , và tên của chương trì nh khả chaỵ cuối cùng . Môṭ vài hê ̣thống đòi hỏi chúng ta cần phải tư ̣ goị tớ i các trình liên kết . Tuy nhiên hầu hết các trình biên dic̣ h hoàn chỉnh đều thưc̣ hiêṇ hô ̣chúng ta công viêc̣ này. Sƣ̉ duṇ g cá c thƣ viêṇ Giờ đây ch úng ta đã biết các thuật ngữ cơ bản , chúng ta có thể hiểu cách thức sử dụng một thư viện. Để sử duṇ g môṭ thư viêṇ cần phải: + Include file header của thư viêṇ + Sử duṇ g các hàm và các biến trong thư viêṇ + Liên kết thư viện vào chương trình khả chạy cuối cùng Các bước này cũng đúng với các module object không có trong các thư viện . Including môṭ file header và liên kết các module object là các bướ c cơ bản để thưc̣ hiêṇ viêc̣ biên dic̣ h đôc̣ lâp̣ trong C và C++. 12
  20. Trình liên kết làm thế nào để tìm một file thƣ viện Khi chúng ta taọ ra môṭ tham chiếu ngoài tớ i môṭ hàm số hoăc̣ môṭ biến số trong C hoăc̣ C++, trình liên kết , khi bắt găp̣ tham chiếu này , có thể thực hiện mộ t trong hai viêc̣ sau: nếu nó chưa thấy phần điṇ h nghiã của hàm hay biến này , nó sẽ thêm định danh vào danh sách các tham chiếu chưa đươc̣ điṇ h nghiã của nó . Nếu như trình liên kết đa ̃ bắt găp̣ điṇ h nghiã của tham chiếu đó , tham chiếu se ̃ đươc̣ sắp xếp laị . Nếu như trình liên kết không tìm thấy điṇ h nghiã của tham chiếu trong danh sách các module object nó se ̃ tiến hành tìm kiếm trong các thư viện . Các thư viện có một vài loại chỉ số nên trình li ên kết không cần thiết phải tìm kiếm hết trong các module objetc của thư viêṇ – nó chỉ cần xem xét các phần chỉ mục . Khi trình liên kết tìm thấy môṭ điṇ h nghiã trong môṭ thư viêṇ , toàn bô ̣module object chứ không chỉ phần đi ̣ nh nghiã của hàm , sẽ được liên kết vào chương trình thực hiện. Chú ý rằng toàn bộ thư viện sẽ không được liên kết, chỉ có phần định nghĩa mà chương trình tham chiếu tới . Như vâỵ nếu chúng ta muốn tối ưu về kích thướ c củ a chương trình chúng ta có thể cho mỗi hàm vào môṭ file khi xây dưṇ g các thư viêṇ riêng của mình. Điều này đòi hỏi công sứ c edit nhiều hơn nhưng cũng có thể có ích . Vì trình liên kết tìm kiếm các file theo thứ tự chúng ta c ó thể che đi sự tồn tại của một hàm thư viện bằng cách dùng hàm của chúng ta với phần định nghĩa và prototype y hệt như hàm thư viện . Tuy nhiên điều này cũng có thế gây ra các lỗi mà chúng ta không thể kiểm soát đươc̣ . Khi môṭ chương trình khả chaỵ đươc̣ viết bằng C hoăc̣ C ++ đươc̣ taọ ra, môṭ số các thành phần nhất định sẽ được liên kết với nó một cách bí mật . Môṭ trong các thành phần này chính là module khởi động (startup), module này chứ a các thủ tuc̣ khở i taọ cần phải đươc̣ thưc̣ hiêṇ bất cứ khi nào môṭ chương trình C hay C ++ bắt đầu chaỵ . Các thủ tục này thiết lâp̣ stack và các biến khở i taọ nhất điṇ h trong chương trình. Trình biên dịch luôn thực hiện vi ệc tìm kiếm trong các thư viện chuẩn để thực hiện liên kết các hàm chuẩn mà chúng ta dùng trong chương trình nên để dùng các hàm trong các thư viện chuẩn chúng ta đơn giản chỉ cần include file header của thư viện đó . Còn đối vớ i các thư viêṇ riêng do chúng ta taọ ra chúng ta cần chỉ rõ tên thư viêṇ cho trình liên kết (chẳng haṇ thư viêṇ graphics không phải là môṭ thư viêṇ chuẩn). 1.2 Chƣơng triǹ h đầu tiên. Cách tốt nhất để học lập trình là xem các chương trình của người khác viết và học tập các kỹ thuật lập trình của họ . Sau đây là chương trình HelloWorld đươc̣ viết bằng C ++, môṭ chương trình mà hầu hết các sách lâp̣ trình đều lấy làm ví du ̣mở đầu. // Chương trình HelloWorld // File hello.cpp // In ra màn hình xâu “Hello, World!” #include // Khai báo luồng cout để sử duṇ g int main() { cout << "Hello, World! I am " 13
  21. << 20 << “ today.” endl; return 0; } Điều đầu tiên chúng ta cần biết là môṭ chương trình C hoăc̣ C ++ là một tập các hàm, biến và các lờ i goị hàm. Khi chương trình thưc̣ hiêṇ nó se ̃ goị đến môṭ hàm đăc̣ biêṭ mà bất cứ chương trình nào cũng có đó là hàm main . Về măṭ thuâṭ toán và nô ̣i dung chương trình này không có gì đặc biệt , nó in ra màn hình một dòng chào mừng : “Hello, World!”. Chúng ta se ̃ lần lươṭ khám phá các đăc̣ điểm của C ++ qua các câu lêṇ h của chương trình đơn giản này. Hai dòng đầu tiên của chương trình là hai dòng chú thích , giớ i thiêụ về chứ c năng của chương trình. C++ chấp nhâṇ kiểu viết chú thích theo kiểu của C: /* chú thích có thể gồm nhiều dòng */ Nhưng đưa ra môṭ kiểu chú thích khác tiêṇ lơị hơn là: // chú thích, chú ý là chú thích này chỉ nằm trên một dòng Môṭ số lâp̣ trình viên thích dùng kiểu chú thích của C++ hơn vì như thế dê ̃ dàng phân biêṭ môṭ chương trình C vớ i môṭ chương trình C ++. Măc̣ dù đây không phải là môṭ qui tắc bắt buôc̣ song chúng ta nên dùng kiểu thứ hai và nếu có dùng kiểu thứ nhất thì cần phải theo môṭ qui luâṭ nhất điṇ h . Tiếp theo là môṭ chỉ thi ̣tiền xử lý #include. Ở đây chúng ta include file header iostream chứ a các dòng vào ra chuẩn của C ++. Thườ ng khi chúng ta include môṭ file header chúng ta nên có kèm môṭ vài chú thích ngắn goṇ về muc̣ đích của file đó , chẳng haṇ ở đây chúng ta include file header iostream là vì cần sử duṇ g đối tươṇ g cout trong thư viêṇ iostream . Tiếp theo là hàm main () có kiểu trả về là int và không nhận tham số nào . Giống như C tất cả các chương trình C ++ đều có một và duy nhất một hàm main() và nếu chúng ta không nói gì có nghĩa là hàm main sẽ trả về một giá trị có kiểu int nên để tránh môṭ vài rắc rối chúng ta nên xác điṇ h kiểu của hàm main là int và trả về 0 trướ c khi kết thúc hàm . Prototype của hàm main là : int main() có nghĩa là hàm này có t hể nhâṇ bất bao nhiêu tham số tuỳ ý . Trong câu lêṇ h tiếp theo chúng ta sử duṇ g đối tươṇ g cout (console output) để in ra một loạt các tham số thông qua các toán tử “ <<”. Chúng ta đa ̃ biết trong ngôn ngữ C toán tử “ <<” là toán tử dịch bit trái nhưng trong C ++ ngoài ý nghĩa là một toán tử dịch bit trái nó còn là một toán tử của đối tượng cout, đó chính là môṭ minh hoạ cho khả năng overload các toán tử của C ++ mà chúng ta sẽ học sau này . Cũng cần chú ý là câu lệnh này được viết trên nhiều dòng , C++ cho phép môṭ câu lêṇ h có thể viết trên nhiều dòng . Trình biên dịch nhận biết sự kết thúc một câu lệnh trong C ++ bằng cách nhận biết sự có mặt của các dấu “;”. endl là môṭ hàm đăc̣ biêṭ thuôc̣ thư viêṇ các luồng vào ra chuẩn nó kết thúc dòng hiêṇ taị của cout là nhảy xuống dòng tiếp theo. Đối tượng cout có khả năng xử lý nhiều tham số tương ứng với các toán tử “ <<”. Nó xem các tham số đó như là môṭ daỹ các ký tự, nếu là các kiểu dữ liêụ khác (ngoài kiểu xâu: các ký tự giữa hai dấu “ và “ ) cout se ̃ có hai cách thứ c xử lý . Thứ nhất nếu đó là các kiểu 14
  22. cơ bản chúng se ̃ đươc̣ chuyển thành môṭ daỹ cá c ký tư ̣ giữa hai dấu “ , còn nếu là một kiểu tư ̣ điṇ h nghiã (lớ p hoăc̣ struct) thì có thể sẽ gọi tới hàm overload toán tử của kiểu đó “ tên biến; Trong đó “kiểu biến” là môṭ kiểu dữ liêụ hơp̣ lê ̣và tên biến là môṭ tên hơp̣ lê ̣theo như điṇ h nghiã trong C. Ví dụ: int a; Khi găp̣ môṭ khai báo như trên trong quá trình biên dic̣ h , trình biên dịch sẽ ngay lập tứ c taọ ra môṭ vùng nhớ (có thể có thêm gía trị khởi tạo ) của biến kiểu số nguyên và gán nhãn là a (xác định hay định nghĩa biến ). Tuy nhiên đôi khi chúng ta chỉ muốn đơn giản khai báo môṭ biến là tồn taị (ở đâu đó trong toàn bộ chương trình chứ không muốn ngay lâp̣ tứ c điṇ h nghiã biến đó ). Để giải quyết trườ ng hơp̣ này chúng ta se ̃ dùng từ khóa extern, ví dụ: extern int a; Khai báo này se ̃ báo cho trình biên dic̣ h biết rằng biến có tên là a là tồn taị và nó đa ̃ hoăc̣ se ̃ đươc̣ điṇ h nghiã đâu đó trong chương trình. Ví dụ: // file: Declare.cpp // Ví dụ khai báo và định nghĩa biến extern int i; // khai báo và không điṇ h nghiã float b; // khai báo và điṇ h nghiã int i; // điṇ h nghiã biến i int main() { b = 1.0; 15
  23. i = 2; } Các biến có thể được khai báo ở bất kỳ một vị trí nào trong chương trình , điều này có đôi chút khác biêṭ so vớ i các chương trình C. 2.2 Tầm hoaṭ đôṇ g củ a cá c biến Khái niệm tầm hoạt động của các biến cho chúng ta biết khu vưc̣ (phần chương trình) mà một biến nào đó có thể được sử dụng hợp lệ và khu vực nào thì việc truy cập tới một biến là không hơp̣ lê ̣. Tầm hoaṭ đôṇ g của môṭ biến bắt đầu từ vi ̣trí mà nó đươc̣ khai báo cho tớ i dấu “ }” đầu tiên khớ p vớ i dấu “ {“ ngay trướ c khai báo của biến đó . Có nghĩa là tầm hoaṭ đôṇ g của môṭ biến đươc̣ xác điṇ h là trong căp̣ “ {“ và “ }” gần nhất bao nó . Tất nhiên tầm hoaṭ đôṇ g của các biến có thể chồng lên nhau. 2.3 Khai bá o biến ngay trong cú phá p củ a cá c câu lêṇ h điều khiển Như chúng ta đa ̃ biết trong các chương trình C ++ viêc̣ khai báo biến là khá tư ̣ do . Các biến có thể được khai báo ở bất kỳ vị trí hợp lệ nào của chương trình miễn là chúng phải là xác định trước khi được sử dụng. Trong ngôn ngữ C và hầu hết các ngôn ngữ thủ tuc̣ khác lâp̣ trình viên bắt buôc̣ phải khai báo các biến taị phần đầu tiên của mỗi thủ tuc̣ . Do đó khi đoc̣ các f ile ma ̃ nguồn C chúng ta luôn thấy một loạt khai báo các biến sẽ được dùng mỗi thủ tục ở phần đầu của thủ tục. Điều này se ̃ rất bất tiêṇ khi môṭ thủ tuc̣ có nhiều biến hoăc̣ dài vì viêc̣ kiểm soát biến (tên, giá trị khở i taọ , tầm hoaṭ ) sẽ trở nên khó khăn . Đi xa hơn cả viêc̣ cho phép khai báo bất kỳ vi ̣trí nào hơp̣ lê ̣trong chương trình C ++ còn cho phép khai báo và khởi tạo các biến ngay bên trong biểu thứ c điều khiển của các vòng lăp̣ for, while, do hoăc̣ trong câu lêṇ h if, switch. Ví dụ: for(int i=0;i<10;i++){ } while(char c = cin.get() != ‟q‟){ . } if(char x = c == „a‟ || c == ‟b‟){ . } switch(int i=cin.get()){ case „A‟: ; break; } 16
  24. Măc̣ dù vâỵ viêc̣ khai báo như trê n chỉ thườ ng đươc̣ dùng vớ i các vòng lăp̣ for vì đôi khi nó gây ra môṭ số lỗi. Ví dụ câu lệnh: while( (char c = cin.get()) !=‟q‟ ){ } sẽ làm chúng ta ngạc nhiên với kết quả nhận được . Vì toán tử != có độ ưu tiên cao hơn toán tử gán = nên c se ̃ nhâṇ môṭ giá tri ̣có kiểu Bool và sau đó mớ i đươc̣ convert sang kiểu char. 2.4 Các kiểu biến Biến toàn cuc̣ (global variable) Các biến toàn cục được định nghĩa bên ngoài tất cả các hàm và có thể được sử dụ ng trong tất cả các phần của chương trình (thâṃ chí ngay cả phần chương trình nằm trong môṭ file ma ̃ nguồn khác). Các biến toàn cục không bị ảnh hưởng bởi các tầm hoạt động (chúng tồn taị cho tớ i khi chương trình kết thúc). Khi cần tham chiếu tớ i các biến toàn cuc̣ trong môṭ file mà nó chưa đươc̣ khai báo (biến này đươc̣ khai báo trong môṭ file khác ) chúng ta sử dụng từ khóa extern để chỉ ra rằng biến đó là môṭ biến toàn cuc̣ đươc̣ khai báo trong file khác. Biến cuc̣ bô ̣(hay điạ phƣơng, local) Các biến địa phương thường được khai báo trong một phạm vi hay tầm hoạt động nhất điṇ h, thườ ng là trong môṭ hàm . Các biến địa phương này còn được gọi là các biến tự đôṇ g vì chúng ta có thể sử duṇ g chúng môṭ cách tư ̣ nhiên trong tầm hoaṭ đôṇ g của chúng và bản thân chúng cũng tự động “out of scope” bên ngoài phạm vi hoạt động . Chúng ta có thể sử duṇ g từ khóa auto để làm rõ hơn điều này. Biến thanh ghi (register variable) Các biến thanh ghi là một loại biến cục bộ . Để khai báo các biến thanh nghi chúng ta dùng từ khóa register. Mục đích của việc khai báo các biến register là báo cho trình biên dịch biết để nó có thể làm cho việc truy cập vào các biến này với tốc độ càng nhanh càng tốt. Viêc̣ tăng tốc đô ̣truy câp̣ biến là phu ̣thuôc̣ vào cài đăṭ tuy nhiên như ngu ̣ý của từ register điều này thườ ng đươc̣ thưc̣ hiêṇ bằng cách đă ̣ t biến vào môṭ thanh ghi . Không có gì đảm bảo là biến được khai báo là register sẽ được đặt trong một thanh ghi hoặc thậm chí tốc đô ̣truy câp̣ se ̃ nhanh hơn . Đó chỉ là môṭ gơị ý cho trình biên dic̣ h . Không thể thưc̣ hiêṇ các biến thanh ghi kiểu này, chúng cũng chỉ có thể là các biến địa phương, không thể là các biến toàn cuc̣ hoăc̣ các biến tiñ h và nói chung chúng ta nên tránh dùng chúng. Biến tiñ h (static variable) Các biến tĩnh được khai báo bằng từ khóa static. Bình thường đối với một biến được khai báo cuc̣ bô ̣trong môṭ hàm số , nó sẽ tự động bị loại bỏ khỏi bộ nhớ khi hàm được gọi thưc̣ hiêṇ xong. Khi hàm đươc̣ goị thưc̣ hiêṇ laị lần nữa , các biến cục bộ lại được khởi tạo lại và cứ thế . Tuy nhiên đôi khi chúng ta muốn lưu laị các giá tri ̣của môṭ biến số đa ̃ có đươc̣ trong các lần goị thưc̣ hiêṇ trướ c của hàm , khi đó viêc̣ dùng biến static là hơp̣ lý . Các biến static chỉ đươc̣ khở i taọ lần đầu tiên khi hàm đươc̣ goị tớ i lần đầu tiên. Chúng ta có thể băn khoăn tư ̣ hỏi là vâỵ taị sao không dùng các biến toàn cuc̣ câu trả lờ i là các biến static có tầm hoạt động trong một thân hàm do đó chúng t a có thể thu hep̣ các lỗi liên quan tới 17
  25. viêc̣ sử duṇ g biến này , có nghĩa khả năng lỗi là thấp hơn so với dùng biến toàn cục . Ngoài ý nghĩa trên từ khóa static thường có một ý nghĩa khác đó là “không thể sử dụng ngoài mộ t phạm vi nhất định”. Khi từ khóa static đươc̣ dùng để khai báo môṭ tên hàm hoăc̣ môṭ biến nằm ngoài tất cả các hàm trong môṭ file ma ̃ nguồn thì có nghiã là biến đó chỉ có tầm hoaṭ đôṇ g trong file đó mà thôi. Khi đó chúng ta nói là biến đó có tầm hoaṭ đôṇ g file. 2.5 Liên kết biến khi biên dic̣ h Để hiểu cách thứ c hoaṭ đôṇ g của các chương trình C và C ++ chúng ta cần phải hiểu quá trình liên kết diễn ra như thế nào . Có hình thức liên kết các biến khi biên dic̣ h: liên kết trong và liên kết ngoài . Liên kết trong có nghiã là bô ̣nhớ (vùng lưu trữ ) đươc̣ taọ ra để biểu diêñ điṇ h danh chỉ cho file đang đươc̣ biên dic̣ h . Các file khác có thể sử dụng định danh đó đối vớ i liên kết trong , hoăc̣ vớ i môṭ biến toàn cuc̣ . Liên kết trong thườ ng đươc̣ thưc̣ hiêṇ vớ i các biến static . Liên kết ngoài có nghiã là mỗi vùng nhớ đươc̣ taọ ra để biểu diêñ điṇ h danh cho tất cả các file đang đươc̣ biên dic̣ h . Các vùng nhớ này chỉ đươc̣ taọ ra môṭ lần và trình liên kết phải sắp xếp laị tất cả các tham chiếu tớ i vùng nhớ đó . Các tên hàm và các biến toàn cục có các liên kết ngoài và chúng có thể được truy cập trong các file khác bằng cách khai báo bằng từ khóa extern . Các biến định nghĩa ngoài các hàm (trừ các const) và các định nghĩa hàm là mặc định đối với liên kết ngoài . Chúng ta có thể buộc chúng thực hiện các liên kết trong bằng từ kh óa static và chỉ rõ liên kết ngoài bằng từ khóa extern. Các biến cục bộ chỉ được sử dụng tạm thời , trên stack khi các hàm đươc̣ goị tới . Trình liên kết không biết tới chúng và do đó không có quá trình liên kết nào được t hưc̣ hiêṇ . 2.6 Các hằng Trong các trình biên dic̣ h C cổ điển chúng ta có thể khai báo các hằng bằng cách sử dụng chỉ thị tiền xử lý, ví dụ: #define PI 3.14159 Khi đó trong quá trình tiền xử lý bô ̣tiền xử lý se ̃ thưc̣ hiêṇ thay thế tất cả các ký hiêụ PI mà nó găp̣ trong các file ma ̃ nguồn bằng giá tri ̣3.14159. Chúng ta vẫn có thể sử dụng cách này trong C ++ tuy nhiên có rất nhiều vấn đề đối vớ i kiểu khai báo này . Chúng ta không thể thực hiệ n kiểm tra kiểu đối vớ i PI , không thể lấy điạ chỉ của PI (vì thế không thể dùng con trỏ trỏ vào biến này ). PI cũng không thể là môṭ biến có kiểu ngườ i dùng điṇ h nghiã . Cũng không thể xác định được tầm hoạt động của PI. C++ sử duṇ g từ khóa const để khai báo các hằng , cú pháp khai báo giống như khai báo biến chỉ khác là giá trị của hằng là không thay đổi . Các hằng trong C++ đều phải khởi tạo trước khi sử dụng . Các giá trị hằng cho các kiểu built-in đươc̣ biểu diêñ như là các số thâp̣ phân, bát phân, số hexa hoăc̣ các số dấu phẩy đôṇ g (đáng buồn là các số nhi ̣phân đươc̣ cho là không quan troṇ g ) hoăc̣ là các ký tự . Nếu không có các chỉ dâñ khai báo nào khác các hằng đươc̣ coi là các số thâp̣ phân . Các hằng bắt đầu bởi số 0 đươc̣ xem là các hằng trong hê ̣bát phân , còn 0x là các hằng trong hê ̣hexa . Các hằng dấu phẩy động được biểu diêñ bở i phần thâp̣ phân và daṇ g mũ hóa ví dụ: 1e4, 1.4e4. Chúng ta có thể thêm các hâụ tố f, F, L, l để chỉ rõ kiểu của các hằng loaị này . Các hằng ký tự được biểu diễn giữa hai dấu „, nếu là ký tư ̣ đăc̣ biêṭ thì có thêm dấu \ đứ ng trướ c. 18
  26. Biến kiểu volatile Trong khi từ khóa const có nghiã là biến không thay đổi giá tri ̣thì khai báo biến với từ khóa volatile có nghiã là chúng ta không biết biến này se ̃ thay đổi lúc nào và do đó trình biên dic̣ h se ̃ không thưc̣ hiêṇ các tối ưu hóa d ựa trên giả thiết về sự ổn định của biến này . Môṭ biến volatile se ̃ đươc̣ đoc̣ vào khi mà giá tri ̣của nó đươc̣ cần đến . Môṭ trườ ng hơp̣ đăc̣ biêṭ của các biến volatile là khi chúng ta viết các chương trình đa luồng . Ví dụ khi chúng ta đang chờ đơị môṭ cờ nào đó đang đươc̣ xử lý bở i môṭ luồng khác thì biến cờ đó bắt buôc̣ phải là volatile . Các biến volatile không có ảnh hưởng gì tới chương trình nếu chúng ta không thưc̣ hiêṇ tối ưu hóa nó nh ưng se ̃ có thể có các lỗi rất tinh vi khi chúng ta tiến hành tối ưu hóa chương trình. 3. Hàm trong C++ Trong ngôn ngữ C cổ (không phải là ngôn ngữ C chuẩn mà chúng ta dùng hiêṇ nay ) chúng ta có thể thực hiện việc gọi hàm với số lươṇ g tham số cũng như kiểu tham số tùy ý mà trình biên dịch sẽ không phàn nàn gì cả . Tất cả dườ ng như đều tốt cho tớ i khi chúng ta chạy chương trình. Có thể chúng ta sẽ nhận được các kết quả rất khó hiểu mà không có bất cứ môṭ dấu hiêụ hay gơị ý nào về chúng . Đây có le ̃ là môṭ trong các lý do làm cho C trở thành một ngôn ngữ được đánh giá là ngôn ngữ Assembly cấp cao . Ngôn ngữ C chuẩn và C++ ngày nay có một cơ chế gọi là nguyên m ẫu hay bản mẫu hàm (function prototype). Vớ i cơ chế này chúng ta cần khai báo kiểu của các tham số của hàm , kiểu của hàm khi khai báo và định nghĩa chúng . Sư ̣ khai báo hay mô tả rõ ràng này đươc̣ goị là biểu mâũ của hàm. Khi hàm đươc̣ goị trình biên dic̣ h se ̃ sử duṇ g biểu mâũ của hàm để kiểum tra xem các tham số đươc̣ truyền có đúng kiểu , số lươṇ g cũng như giá tri ̣trả về của hàm có đươc̣ xử lý đúng hay không . Nếu như có các lỗi trong quá trình kiểm tra xảy ra trình biên dịch sẽ thông báo ngay cho lâp̣ trình viên biết trong quá trình biên dic̣ h. Cú pháp khai báo một hàm như sau: ( ); Ví dụ: int max(int x, int y); Về bản chất chúng ta không cần có các tên tham biến , chúng chỉ thực sự cần khi chúng ta sử dụng chúng trong việc định nghĩa các hàm . Tuy nhiên điều này cũng không phải là bắt buộc đối vớ i C++ (trong C là bắt buôc̣ ). Chúng ta có thể có một tham số nào đó không có tên và nó se ̃ không đươc̣ sử duṇ g trong thân hàm (tất nhiên vì nó không có tên ). Khi chúng ta goị tớ i hàm đó chúng ta vâñ phải truyền đúng c ác tham số. Tuy nhiên tác giả của hàm đó sau đó vẫn có thể sử dụng tham số ở đâu đó mà không cần thiết phải thay đổi các lời gọi hàm . Điều này rất tiêṇ khi chúng ta không muốn có các lờ i cảnh báo về viêc̣ không sử duṇ g môṭ tham số nào đó trong thân hàm . C và C ++ có hai cách khác nhau để điṇ h nghiã danh sách các tham số . Nếu chúng ta có môṭ hàm func (), C++ sẽ hiểu là hàm này không có tham số , C laị hiểu là hàm này có thể có bất kỳ th am số nào . Môṭ hàm func(void) sẽ được hiểu là không có tham số trong cả C và C ++. Môṭ trườ ng hơp̣ nữa xảy ra là khi chúng ta không xác điṇ h đươc̣ số tham số cũng như kiểu tham số của hàm mà chúng ta muốn khai báo (gọi là mộ t danh sách tham biến : variable argument list ). Khi đó chúng ta sẽ sử dụng ký pháp ( ). Tuy nhiên nên haṇ chế sử duṇ g nó trong C ++, chúng ta có nhiều cách khác để đạt được kết quả này mà không cần tới ký pháp đó. 19
  27. Các giá trị trả về của hàm Trong nguyên mâũ hàm chúng ta buôc̣ phải chỉ rõ kiểu của hàm , nếu môṭ hàm không có kiểu trả về thì kiểu của nó là void . Trong mỗi môṭ thân hàm có kiểu bao giờ cũng có ít nhất môṭ câu lêṇ h return . Khi găp̣ lêṇ h này trong quá trình thưc̣ hiêṇ , hàm sẽ kết thúc.Trong các hàm không kiểu cũng có thể dùng return để thoát khỏi hàm . Môṭ trong các điểm maṇ h của ngôn ngữ C và C ++ là một thư viện hàm rất phong phú và linh hoạt . Để sử dụng chúng, lâp̣ trình viên chỉ cần thưc̣ hiêṇ include các file header chứ a các prototype của chúng trong chương trình , phần còn laị se ̃ tư ̣ do trình biên dic̣ h và trình liên kết thưc̣ hiêṇ . Chúng ta có thể tạo ra các thư vi ện hàm riêng cho mình để sử dụng . Tuy nhiên haỹ xem kỹ phần manual của trình biên dic̣ h trướ c khi thưc̣ hiêṇ . 4. Các cấu trúc điều khiển Các câu lệnh điều khiển là điều mà mọi lập trình viên cần phải biết trước khi viết bấ t cứ môṭ chương trình nào . Chúng ta có các câu lệnh điều khiển : if-else, while, do, do- while, for và câu lệnh lựa chọn switch. Các câu lệnh điều kiện dựa trên kết quả đúng hoặc sai của một biểu thức điều kiện để xác định đường đi của chương trình . Trong C++ hai từ khóa true và false đa ̃ đươc̣ đưa vào để biểu thị cho kết quả đúng hoặc sai của một biểu thức điều kiện, tuy nhiên các qui ước cũ vâñ có thể đươc̣ dùng : môṭ gía tri ̣bất kỳ khác 0 sẽ đươc̣ coi là đúng và môṭ gía tri ̣bằng 0 có nghĩa là sai. 4.1 Câu lêṇ h if-else Câu lêṇ h điều kiêṇ if – else có thể được sử dụng dưới hai dạng khác nhau : có hoặc không có phần mêṇ h đề else. Cú pháp của hai dạng này như sau: if(expression) statement hoăc̣ if(expression) statement else statement Biểu thứ c expression cho môṭ giá tri ̣true hoăc̣ false . Phần câu lêṇ h “statement” có thể là môṭ câu lêṇ h đơn kết thúc bằng môṭ dấu chấm phẩy cũng có thể là môṭ câu lêṇ h hơp̣ thành, môṭ tâp̣ các câu lêṇ h đơn giữa hai dấu { và }. Chú ý là phần câu lệnh statement cũng có thể bao gồm các câu lệnh điều kiện if – else. 4.2 Vòng lặp không xác định while Cú pháp: while (expression) statement 20
  28. Biểu thứ c đươc̣ tính toán lần đầu tiên taị thờ i điểm bắt đầu của vòng lặp và sau đó đươc̣ tính laị mỗi khi lăp̣ laị quá trình thưc̣ hiêṇ câu lêṇ h . Điều kiêṇ để dừ ng vòng lăp̣ không xác điṇ h while là giá tri ̣của biểu thứ c expression bằng false . Như vâỵ điều cần chú ý ở đây là câu lệ nh trong thân vòng lăp̣ có thể không đươc̣ thưc̣ hiêṇ trong trườ ng hơp̣ biểu thứ c điều kiêṇ cho giá tri ̣false ngay lần đầu tính toán . Đôi khi chúng ta không cần sử duṇ g biểu thứ c điều kiêṇ để kết thúc vòng lăp̣ while , đó cũng l à trường hợp đơn giản nhất của biểu thứ c điều kiêṇ . 4.3 Vòng lặp không xác định do – while Sư ̣ khác biêṭ của vòng lăp̣ do – while so vớ i vòng lăp̣ while là vòng lăp̣ do – while thưc̣ hiêṇ ít nhất môṭ lần ngay cả khi biểu thứ c điều kiêṇ cho giá tri ̣false trong lần tính toán đầu tiên. Cú pháp của vòng lặp do – while: do Statement while(expression); Vì một vài lý do các lập trình viên thường ít sử dụng vòng lặp do – while hơn so với vòng lặp while. 4.4 Vòng lặp xác định for Vòng lặp for thực hiện một thao tác khởi tạo trước khi thực hiện lần lặp đầu tiên . Sau đó thưc̣ hiêṇ quá trình kiểm tra điều kiêṇ thưc̣ hiêṇ của vòng lăp̣ , và tại cuối mỗi lần thực hiêṇ của vòng lăp̣ thưc̣ hiêṇ môṭ thao tác nhảy qua môṭ số giá tri ̣nào đó của biến điều khiển. Cú pháp: for(initialization; conditional; step) statement Bất kỳ biểu thứ c nào trong các biểu thứ c initialization , conditional và step đều có thể là các biểu thức rỗng tuy nhiên trong trường hợp đó cần giữ lại các dấu chấm phẩy . Biểu thứ c khở i taọ chỉ đươc̣ thưc̣ hiêṇ lần đầu tiên trướ c khi vòng lăp̣ đươc̣ thưc̣ hiêṇ . Biểu thứ c conditional se ̃ đươc̣ kiểm tra mỗi khi vò ng lăp̣ thưc̣ hiêṇ và nếu nó nhâṇ giá tri ̣false ngay lần đầu tiên thân của vòng lăp̣ se ̃ không đươc̣ thưc̣ hiêṇ . Tại thời điểm kết thúc của thân vòng lặp biểu thức step sẽ được thực hiện. Tuy có sư ̣ khác nhau song về bản chất các vòng lặp for , while và do – while có sư ̣ tương đồng và chúng đều có thể chuyển đổi cho nhau . Vòng lặp for được sử dụng nhiều hơn do môṭ số nguyên nhân sau: Trong vòng lăp̣ for có sư ̣ khở i taọ ban đầu , đồng thờ i nó giữ ch o các câu lêṇ h gần nhau hơn và dê ̃ thấy từ đỉnh chu trình , đăc̣ biêṭ là khi chúng ta có nhiểu chu trình lồng nhau. Ví dụ: Thuâṭ toán sắp xếp Shell – sort: Ý tưởng của thuật toán này là ở mỗi bước thay vì so sánh và đổi ch ỗ hai phần tử kề nhau như trong phương pháp sắp xếp đơn giản chúng ta sẽ so sánh và đổi chỗ hai phần tử cách xa nhau . Điều này hướ ng tớ i viêc̣ loaị bỏ quá nhiều sư ̣ mất trâṭ tư ̣ môṭ cách nhanh chóng cho nên ở các giai đoaṇ sau còn ít công viêc̣ phải làm . 21
  29. Khoảng cách giữa các phần tử so sánh cũng được giảm dần tới một lúc việc xắp xếp trở thành việc đổi chỗ hai phần tử kề nhau. void shellSort(int * a, int n){ int gap, i, j, temp; for(gap = n/2; gap > 0; gap /= 2) for(i = gap; i =0 && a[i] > a[i + gap]; j -= gap){ temp = a[j]; a[j] = a[j + gap]; a[j + gap] = temp; } } Môṭ chú ý thứ hai là các toán tử dấu phẩy cũng thườ ng đươc̣ sử duṇ g vớ i các biểu thứ c trong phần điều khiển của vòng lăp̣ for ví du ̣như khi để điểu khiển nhiểu biến chỉ số chẳng haṇ : char * reverse(char *s){ int c, i, j; for(i = 0, j = strlen(s) – 1; i < j; i++, j ) { c = s[i]; s[i] = s[j]; s[j] = c; } } 4.5 Các từ khóa break và continue Chúng ta có thể thực hiện điều khiển việc thực hiện trong thân các vòng lặp bằng các câu lêṇ h break và continue. Câu lêṇ h break sẽ thoát khỏi thân vòng lặp và không thực hiêṇ phần còn lại, câu lêṇ h continue quay trở laị thưc̣ hiêṇ bướ c lăp̣ tiếp theo (bỏ qua phần các câu lệnh nằm sau nó trong vòng lặp). Lêṇ h break là cần thiết để thoát khỏi các vòng lăp̣ mà điều kiện thực hiện luôn luôn đúng chẳng hạn như while(true) . 4.6 Câu lêṇ h lƣạ choṇ switch Câu lêṇ h switch lưạ choṇ thưc̣ hiêṇ các câu lêṇ h trong môṭ nhóm các câu lêṇ h dưạ trên giá tri ̣của môṭ biểu thứ c nguyên. Cú pháp của nó như sau: switch(biến lưạ choṇ ) 22
  30. { case integral_value1: statement; break; case integral_value2: statement; break; ( ) default: statement; } Biểu thứ c lưạ choṇ sau khi thưc̣ hiêṇ se ̃ cho môṭ giá tri ̣nguyên . Giá trị nguyên này đươc̣ so sánh vớ i mỗi giá tri ̣hoăc̣ môṭ số giá tri ̣ nguyên, nếu trùng khớ p câu lêṇ h tương ứng sẽ được thực hiện. Nếu không có sư ̣ trùng khớ p nào xảy ra câu lêṇ h trong phần default sẽ được thực hiện. Chú ý rằng câu lệnh break ngay sau mỗi phần lựa chọn case có thể không cần sử dụng tuy nhiên khi đó các câu lệnh tiếp sau lựa chọn đó sẽ được thực hiện cho tới khi gặp phải một lệnh break. Nếu có lêṇ h break các câu lêṇ h tiếp sau lưạ choṇ se ̃ không đươc̣ thưc̣ hiêṇ , chương trình se ̃ thoát khỏi thân lêṇ h switch. 4.7 Câu lêṇ h goto Câu lêṇ h goto cũng là môṭ câu lêṇ h cơ bản của C ++ vì nó có trong C . Nói chung là chúng ta nên tránh dùng goto tuy vậy có một số trường hợp việc dùng goto cũng có thể chấp nhâṇ đươc̣ như khi chúng ta muốn thoát hoàn toàn ra khỏi tất cả các vòng lăp̣ lồng nhau từ vòng lăp̣ trong cùng. 4.8 Đệ qui Đê ̣qui là môṭ kỹ thuâṭ thườ ng đươc̣ dùng để giải quyết các vấn đề có đô ̣phứ c tap̣ không xác điṇ h khi mà chúng ta thườ ng không cần phải lo lăng về kích thướ c bô ̣nhớ cần sử duṇ g. Các bài toán đệ qui thường được giải quyết theo chiến lược chia để trị. 5. Các kiểu dữ liệu cơ bản của C++ Các kiểu dữ liệu cơ bản của C ++ hầu hết đều kế thừ a của C ngoại trừ kiểu bool với hai hằng số true và false. Đặc tả của ngôn ngữ C chuẩn cho các kiểu dữ liệu built – in không chỉ rõ cu ̣thể các kiểu dữ liêụ này cần bao nhiêu bit . Thay vào đó nó qui điṇ h các giá tri ̣max và min cá c bit mà mỗi kiểu dữ liệu có thể chứa . Khi đó tuỳ thuôc̣ vào hê ̣thống nền mà chúng ta sử duṇ g các biến của cùng một chương trình sẽ có kích thước khác nhau khi biên dịch trên các hệ thống khác nhau . Ví dụ với các chươn g trình chaỵ trên DOS các biến kiểu int se ̃ có kích thướ c là 2 byte tứ c 16 bit nhưng trên Linux kiểu int se ̃ là 32 bit tứ c 4 byte. Các giá trị giới hạn này được định nghĩa trong hai file header hệ thống là limit.h và float.h. Về cơ bản cả C và C ++ đều có 4 kiểu dữ liêụ built -in là char , int, float và double . Kiểu char có kích thướ c nhỏ nhất là 8 bit măc̣ dù thưc̣ tế có thể lớ n hơn . Kiểu int có kích thướ c nhỏ nhất là 2 byte còn kiểu float và double l à hai kiểu số thực có độ chính xác đơn và kép, chúng có format tuân theo chuẩn IEEE. Như đa ̃ đề câp̣ ở trên kiểu bool là môṭ kiểu chuẩn của ngôn ngữ C ++ vớ i hai giá tri ̣là true và false . Tuy nhiên rất nhiều chương trình vâñ d ùng các giá trị kiểu int thay cho các 23
  31. giá trị kiểu bool nên trong các trường hợp cần đến một giá trị bool trình biên dịch thường thưc̣ hiêṇ chuyển kiểu từ int sang bool hoăc̣ có cảnh báo cho chúng ta để chính xác hóa các trường hợp này. Ngoài 4 kiểu trên ra chúng ta có thể sử duṇ g các từ khóa bổ trơ ̣ sau để mở rôṇ g khả năng lưu trữ của chúng . C++ cung cấp 4 từ khóa bổ trơ ̣ là : long, short, signed và unsigned. long và short đươc̣ dùng để chỉ đi ṇ h các giá tri ̣max và min mà môṭ kiểu dữ liêụ sẽ lưu giữ. Môṭ biến kiểu int se ̃ có kích thướ c bằng kích thướ c nhỏ nhất của môṭ biến kiểu short int. Các kiểu dữ liệu số nguyên có thể là: short int và long int. Vớ i các kiểu thực ta có long float và long double, không có các kiểu số thưc̣ vớ i từ khóa short . Các từ khóa signed và unsigned được dùng để chỉ định cho trình biên dịch cách thức sử dụng bit dấu với các kiểu nguyên và kiểu char . Vớ i môṭ kiểu signed bit cao nhất đươc̣ dùng làm bit dấu , kiểu này chỉ cần thiết với char . Vớ i kiểu unsigned không cần dùng môṭ bit làm bit dấu nên số phần tử dương se ̃ tăng lên gấp đôi. Kiểu con trỏ và tham chiếu Có thể có nhiều cách nói khác nhau về các biến con trỏ , môṭ trong những điểm maṇ h mẽ và mềm dẻo nhất của ngông ngữ C , đồng thờ i cũng là nguyên nhân gây ra nhiều rắc rối vớ i các chương trình viết bằng C . Con trỏ là môṭ kiểu dữ liêụ bìn h thườ ng như các kiểu dữ liêụ khác chỉ có môṭ điều đăc̣ biêṭ là giá tri ̣của nó là điạ chỉ của các biến khác . Chính vì điều đăc̣ biêṭ đó mà thông qua các biến con trỏ chúng ta có thể thưc̣ hiêṇ các thao tác đối vớ i môṭ biến số khác ví du ̣như thay đổi giá tri ̣ . Xét ví dụ sau đây: #include int main(int argc, char *argv[]) { int n = 9; int * pn = &n; cout << pn << "\n" << *pn << "\n" << &pn << "\n" << &n; return 0; } output nhâṇ đươc̣ khi chạy chương trình trên là: 0x1491ffc0 9 0x1491ffbe 0x1491ffc0 Giá trị của pn đúng bằng địa chỉ của biến n và *pn bằng giá tri ̣của n. Toán tử * đươc̣ gọi là toán tử tham chiếu lại (dereference), nó có thể được dùng để khai bá o biến con trỏ , tham chiếu tớ i giá tri ̣của biến mà môṭ biến con trỏ trỏ tớ i. Hai ứ ng duṇ g cơ bản của con trỏ là: 24
  32. + thay đổi giá tri ̣của các đối tươṇ g bên ngoài môṭ hàm số . Đây là ứ ng duṇ g cơ bản nhất của các biến con trỏ. + các kỹ thuật lập trình tinh vi khác mà chúng ta sẽ học sau. Tham chiếu (reference) Ấn tượng ban đầu của chúng ta về các tham chiếu là chúng không cần thiết . Chúng ta có thể viết các chương trình mà không cần tới các tham chiếu. Điều này nói chung là đúng trừ môṭ số truờ ng hơp̣ mà chúng ta se ̃ hoc̣ sau này. Thưc̣ ra khái niêṃ truyền biến qua tham chiếu (pass by reference ) không phải là môṭ khái niêṃ chỉ có ở C ++, nó cũng là một phần cơ bản trong một số ngôn ngữ khác . Khái niệm về tham chiếu cũng có sự tương đồng với khái niệm con trỏ : chúng ta có thể truyền địa chỉ của một tham biến (trong môṭ hàm ) qua môṭ tham chiếu . Tuy nhiên sư ̣ khác nhau giữa con trỏ v à tham chiếu là truyền bằng tham chiếu có vẻ sac̣ h se ̃ hơn (cleaner) so vớ i con trỏ . Tham chiếu cho phép các hàm có thể thay đổi giá tri ̣của các đối tươṇ g ngoài như con trỏ tuy nhiên trong cú pháp có sư ̣ khác nhau chút ít: Trong danh sách tham số của hàm chúng ta dùng khai báo int & n để báo rằng chúng ta muốn truyền bằng tham chiếu và truy câp̣ bình thườ ng như môṭ biến khác (con trỏ cần dùng dấu * để truy cập tới giá trị biến). Khi goị hàm cú pháp đối với việc truyền bằng tham chiếu tương tư ̣ như truyền bằng giá tri ̣(vớ i con trỏ cần thêm 1 dấu & trướ c tên biến). 6. Môṭ số toá n tƣ̉ trong C++ Tất cả các toán tử đều sinh ra môṭ kết quả nào đó từ các toán haṇ g củ a chúng. Giá trị này được sinh ra mà không làm thay đổi gía trị của các toán hạng , trừ toán tử gán , toán tử tăng và giảm. Thay đổi giá tri ̣của môṭ toán haṇ g đươc̣ goị là 1 hiêụ ứ ng phu.̣ 6.1 Toán tử gán (assignment operator) Phép gán được thực hiện bằng toán tử =. Toán tử gán có nghĩa là lấy giá trị bên phải của toán tử (thườ ng đươc̣ goị là rvalue) và copy giá trị đó sang bên trái của toán tử (thường đươc̣ goị là lvalue ). Môṭ rvalue có thể là bất kỳ giá trị hằng , biến, hoăc̣ biểu thứ c có thể sinh ra môṭ giá tri,̣ nhưng môṭ lvalue nhất thiết phải là môṭ biến đươc̣ đăṭ tên phân biêṭ (có nghĩa là phải có một vùng nhớ vật lý để chứa dữ liệu ). Ví dụ chúng ta có thể gán một giá trị hằng số cho một biến chứ không thể gán một biến cho một giá trị hằng. 6.2 Các toán tử toán học Các toán tử toán học gồm có phép cộng (+), phép trừ (-), phép nhân (*) và phép chia (/), phép lấy phần dư (%). Phép chia các số nguyên được thực hiện như là phép div , phép lấy phần dư không thưc̣ hiêṇ đươc̣ vớ i các số dấu phẩy đôṇ g. Cả C và C ++ đều có một cơ chế viết các câu lệnh tắt cho phép thực hiện đồng th ời môṭ phép gán và môṭ toán tử toán hoc̣ . Điều này đươc̣ thưc̣ hiêṇ bằng cách viết môṭ toán tử trướ c môṭ dấu =. Ví dụ: x += 4; 6.3 Các toán tử quan hệ Các toán tử quan hệ thiết lập một quan hệ giữa các giá trị của to án hạng. Chúng sinh ra môṭ giá tri ̣có kiểu Boolean , là true nếu quan hệ đó là đúng và false nếu như quan hệ đó là sai. Các toán tử quan hệ gồm có : nhỏ hơn ( ), nhỏ hơn hoặc bằng (<=), lớn 25
  33. hơn hoăc̣ bằng và bằn g. Các toán tử này đều có thể sử dụng với các kiểu dữ liệu built -in của C và C++. 6.4 Các toán tử logic Các toán tử logic and (&&) và hoặc (||) cho ta kết quả là true hoăc̣ false dưạ trên mới quan hê ̣logic giữa các tham số của chúng. Chú ý rằng trong C và C++ true có nghiã là môṭ giá trị khác 0 và false có nghĩa là một giá trị bằng 0. Khi in ra màn hình true se ̃ là 1 và false sẽ là 0. Viêc̣ sử duṇ g các toán tử logic này cũng không có gì đă ̣ c biêṭ chỉ cần chú ý đối với các số dấu phẩy động, ví dụ: float t = 1.22222e12123123, f = 1.22223e12123123; cout >) và toán tử dịch bit trái ( ), toán tử new và delete cũng là các toán tử môṭ ngôi của C++. 6.8 Toán tử 3 ngôi Toán tử 3 ngôi if-else thườ ng ít đươc̣ sử duṇ g bở i vì nó đòi hỏi có 3 toán hạng. Đây thưc̣ sư ̣ là môṭ toán tử bở i vì nó cho ta môṭ giá tri ̣chứ không giống vớ i câu lêṇ h if -else bình thường. Nó bao gồm 3 biểu thứ c: nếu biểu thứ c đầu tiên (sau biểu thứ c đầu tiên là môṭ 26
  34. dấu ?) cho ta môṭ giá tri ̣true thì biểu thứ c ngay sau dấu ? sẽ được thực hiện và kết qủa của nó trở thành kết quả sinh ra bởi toán tử. Nếu như biểu thức đầu tiên cho một gía trị false thì biểu thứ c thứ 3 (sau dấu :) sẽ được thực hiện và kết quả của nó sẽ là kết quả của toán tử. Ví dụ: int max(int a, int b){ return (a>b)?a:b; } 6.9 Toán tử dấu phẩy Dấu phẩy không chỉ haṇ chế sử duṇ g trong viêc̣ khai báo các biến , danh sách tham số của một hàm số mà nó còn là một toán tử được sử dụng để tách biệt các biểu thức . Trong trườ ng hơp̣ là môṭ toán tử , kết quả của toán tử dấu phẩy se ̃ là kết quả của viêc̣ thưc̣ hiêṇ biểu thứ c cuối cùng . Các biểu thức khác cũng được thực hiện và có thể ảnh hưởng tới kết qủa của việc thực hiện của toán tử này qua các hiệu ứng phụ của chúng . Thườ ng thì toán tử dấu phẩy cũng không đươc̣ sử duṇ g nhiều vì moị ngườ i thườ ng có thói quen không xem đây là môṭ toán tử . 6.10 Các lỗi thƣờng gặp khi sử dụng các toán tử Môṭ lỗi thườ ng găp̣ khi sử duṇ g các toán tử là chúng ta kh ông sử duṇ g các căp̣ đóng mở ngoăc̣ thườ ng xuyên. Ví dụ: a = b++, c++, d++; Câu lêṇ h trên hoàn toàn khác vớ i câu lêṇ h: a = (b++, c++, d++); Tiếp theo là lỗi khi so sánh hai toán haṇ g bằng toán tử gán =. Tương tư ̣ chúng ta cũng hay nhầm lâñ khi sử duṇ g các toán tử bitwise và các toán tử logic tương tư ̣ vớ i chúng. 6.11 Toán tử chuyển kiểu Toán tử chuyển kiểu thường được sử dụng khi chúng ta muốn thực hiện một số toán tử của môṭ kiểu dữ liêụ nào đó vớ i môṭ biến thuôc̣ môṭ kiểu dữ liêụ khác. Ví dụ: float f = 100.972; int n = (int)f; Đó là cách chuyển kiểu trong ngôn ngữ C . C++ cung cấp cho chúng ta môṭ cách chuyển kiểu khác, tương đối giống vớ i cách goị hàm: float f = 100.972; int n = int(f); Chuyển kiểu cung cấp cho chúng ta môṭ cơ chế rất maṇ h để xử lý các biến tuy nhiên đôi khi nó cũng gây ra nhiều lỗi làm chúng ta đau đầu . Vì để thực hiện chuyển kiểu đôi khi các biến cần có thêm bộ nhớ để chứa dữ liệu và điều này không phải bao giờ cũng suôn sẻ 27
  35. nhất là khi chúng ta thưc̣ hiêṇ chuyển kiểu các con trỏ . Chuyển kiểu thườ ng chỉ thưc̣ hiêṇ vớ i các biến hơn là các hằng. Trong C++ chúng ta có 4 loại hình chuyển kiểu là static_cast, const_cast, reinterpret_cast và dynamic_cast. 6.12 Toán tử sizeof. Toán tử sizeof cho chúng ta biết số lượng byte được sử dụng bởi một biến cụ thể . Nó cũng có thể cho ta biết kích thước cụ thể của một k iểu dữ liêụ . Chú ý rằng sizeof là một toán tử chứ không phải là một hàm vì thế trừ trường hợp sử dụng với các kiểu dữ liệu với các biến số chúng ta không cần có các dấu đóng, mở ngoăc̣ . 7. Các kiểu dữ liệu ngƣời dùng định nghĩa Các kiểu dữ liệu cơ bản và các biến thái của chúng là cần thiết tuy nhiên nếu chỉ dùng chúng thì cũng không thể tạo nên các chương trình có ý nghĩa được . C và C++ cung cấp cho chúng ta rất nhiều cơ chế k hác nhau để xây dựng lên các kiểu tích hợp có ý nghĩa hơn, phứ c tap̣ hơn và phù hơp̣ vớ i nhu cầu của chương trình hơn . Kiểu dữ liêụ ngườ i dùng điṇ h nghiã quan troṇ g nhất của C là struct , và của C++ là class. Tuy nhiên các dễ nhất để điṇ h nghiã môṭ kiểu mớ i là dùng từ khóa typedef để đặt bí danh cho một kiểu sẵn có. Thiết lâp̣ cá c tên bí danh vớ i tƣ̀ khó a typedef Typedef có nghiã là “type definition” nhưng những gì mà từ khóa này thưc̣ sư ̣ làm không giống như đúng ngữ nghiã của hai từ “type definition” . Cú pháp sử dụng với từ typedef: typedef Ví dụ: typedef unsigned long ulong; typedef struct str_list{ int data; struct str_list * next; }list; Sau khi khai báo như trên chúng ta có thể khai báo trong chương trình như sau: list * aList, anotherList; Và trình biên dịch gặp từ ulong với một khai báo biến nó sẽ hiểu rằng đó chính là kiểu unsigned long. Chúng ta có thể cho rằng điều này có thể thưc̣ hiêṇ dê ̃ dàng bằng các thao tác thay thế trong giai đoaṇ tiền xử lý song thưc̣ ra không phải như vâỵ vì đôi khi trình biên dic̣ h buôc̣ phải hiểu rằng chúng ta muốn nó xử lý môṭ tên nào đó như là môṭ kiểu dữ liêụ (ví dụ thực hiện so sánh hai biến chẳng hạn ) vì thế nên từ khóa typedef là thực sự cần thiết. Từ khóa typedef thườ ng đươc̣ dùng khi chúng ta khai báo các cấu trúc mớ i hoăc̣ đăṭ bí danh cho một kiểu con trỏ nào đó. Kiểu dƣ̃ liêụ cấ u trú c vớ i tƣ̀ khó a struct Kiểu dữ liêụ cấu trúc là môṭ cách cho phép các lâp̣ trình viên nhóm môṭ nhóm các biến thuôc̣ các kiểu dữ liêụ khác nhau taọ thành môṭ cấu trúc . Vớ i môṭ kiểu struct chúng ta có thể truy cập tới các thành phần dữ liệu qua các toán tử tham chiếu “ .” và “->” vớ i môṭ 28
  36. con trỏ cấu trúc. Có một điều đặc biệt khi chúng ta khai báo và sử dụng các cấu trúc trong C++: typedef struct{ // hoặc có thể là: typedef struct list int data; list *next; }list; main(){ list * ptr; } Kiểu dƣ̃ liêụ liêṭ kê (enum) Kiểu dữ liêụ liêṭ kê đươc̣ khai báo bằng từ khóa enum và thườ ng đươc̣ dùng để làm chương trình sáng sủa hơn. Ví dụ: enum Bool{true = 1, false = 0}; Thườ ng trong C ++ kiểu enum đươc̣ dùng ít hơn do môṭ số nguyên nhân ví du ̣như kiểm tra kiểu, chẳng haṇ chúng ta không thể thưc̣ hiêṇ lêṇ h true++. Kiểu hơp̣ nhấ t (union) Đôi khi trong chương trình chúng ta cần phải làm viêc̣ vớ i nhiều kiểu dữ liêụ khác nhau vớ i cùng môṭ biến số ., khi đó chúng ta có thể thưc̣ hiêṇ theo hai cách : môṭ là dùng kiểu dữ liêụ cấu trúc hoăc̣ nếu có thể sử duṇ g kiểu hơp̣ nhất để tiết kiêṃ bô ̣ nhớ . Kiểu union có cách khai báo giống hêṭ kiểu struct chỉ có khác ở cách sử duṇ g : tại một thời điểm chúng ta chỉ có thể truy cập tới một thành phần của một biến kiểu union và kích thước của môṭ kiểu dữ liêụ union ch ính bằng kích thước của thành phần có kích thước lớn nhất của kiểu. Ví dụ: union test_type{ char c; int i; float f; double d; }; #include union Packed { // khai báo giống như môṭ lớ p char i; short j; int k; 29
  37. long l; float f; double d; // kích thước bằng kích thước của kiểu double }; // kết thúc bằng dấu ; giống như kiểu struct int main() { cout #include void func1(int a[], int size) { for(int i = 0; i < size; i++) a[i] = i * i - i; } 30
  38. void func2(int* a, int size) { for(int i = 0; i < size; i++) a[i] = i * i + i; } void print(int a[], string name, int size) { for(int i = 0; i < size; i++) cout << name << "[" << i << "] = " << a[i] << endl; } int main() { int a[5], b[5]; print(a, "a", 5); print(b, "b", 5); // khở i taọ mảng: func1(a, 5); func1(b, 5); print(a, "a", 5); print(b, "b", 5); func2(a, 5); func2(b, 5); print(a, "a", 5); print(b, "b", 5); } Môṭ trườ ng hơp̣ đăc̣ biêṭ của các hàm kiểu này chính là bản thân hàm main . Hàm main có hai tham số là (int n, char * ags[]). Để thuâṇ tiêṇ cho viêc̣ sử duṇ g các tham số này chúng ta nên dùng các hàm chuyển kiểu chẳng hạn như: atoi(), atof(), atol(). 8. Bài tập Bài tập 1: Viết chương trình nhập vào từ bàn phím một số n và in ra ma trận theo qui luật sau: - với n = 1 in ra ma trận gồm 1 số 10 10 20 - với n = 2 in ra ma trận gồm 3 số : 10 10 20 30 - với n = 3 in ra ma trận gồm 5 số: 10 20 10 31
  39. - vân vân Bài tập 2: Viết chương trình nhập vào một số từ bàn phím và in ra ma trận tương ứng theo qui luật sau: 0 1 - n = 1 in ra ma trận: 1 0 0 1 2 - n = 2 in ra ma trận: 1 2 0 2 0 1 0 1 2 3 1 2 3 0 - n = 3 in ra ma trận sau: 2 3 0 1 3 0 1 2 - vân vân Bài tập 3: Viết chương trình nhập vào một số và in ra ma trận số tương ứng theo qui luật sau: - n = 1 in ra ma trận: 0 1 2 - n = 2 in ra ma trận: 2 0 1 1 2 0 0 1 2 3 3 0 1 2 - n = 3 in ra ma trận: 2 3 0 1 1 2 3 0 - vân vân Bài tập 4: Viết chương trình nhập vào 1 số từ bàn phím và in ra ma trận theo qui luật sau: - n = 1 in ra ma trận gồm 1 số: 1 1 2 - n = 2 in ra ma trận: 4 3 1 2 3 - n = 3 in ra ma trận: 8 9 4 7 6 5 1 2 3 4 12 13 14 5 - n = 4 in ra ma trận: , vân vân 11 16 15 6 10 9 8 7 Bài tập 5: nhập vào từ bàn phím một xâu văn bản (các ký tự ASCII không là ký tự điều khiển, có độ dài không vượt quá 255) là tên một người. Hãy viết chương trình thực hiện yêu cầu sau: a) đếm số lần xuất hiện của các ký tự trong xâu b) hiển thị theo kiểu đồ thị như sau: chẳng hạn với xâu “ab1eb8bbedaddae1aeb” (a:4, b:5, 8:1, d:3, e:4, 1:2) * 32
  40. * * * * * * * * * * * * * * * * * * a b 8 d e 1 Bài tập 6: Viết chương trình nhập vào một số N hãy in ra màn hình tất cả các số nguyên tố và số hoàn hảo nhỏ hơn N. 33
  41. CHƢƠNG III: CON TRỎ, THAM CHIẾ U VÀ HÀ M. 1. Hàm trong C++ Các hàm là công cụ chính cho phép xây dựng các chương trình lớn trong C và C ++. Vớ i các hàm nhỏ môṭ chương trình lớ n có thể đươc̣ chia thành các chương trình nhỏ hơn , dê ̃ giải quyết hơn. Vớ i các ngôn ngữ lâp̣ trình khác nhau ngườ i ta dùng các thuâṭ ngữ khác nhau để chỉ môṭ chương trình con , trong C và C ++ các chương trình con được gọi là các hàm. 1.1 Nguyên mẫu và điṇ h nghiã hàm Môṭ hàm trong C và C ++ thƣờng đƣơc̣ khai bá o nguyên mẫu trƣớ c khi thƣc̣ sƣ ̣ cài đặt (điṇ h nghiã ). Cú pháp khai báo nguyên mẫu của một hàm nhƣ sau: ( ); Trong đó là kiểu dữ liệu mà hàm trả về , là tên mà chúng ta muốn đăṭ cho hàm (tên này đươc̣ dùng để goị hàm ), danh sách tham số là danh sách các tham biến và kiểu của chúng đươc̣ sử duṇ g vớ i hàm. Ví dụ: int max(int a, int b); Thườ ng các nguyên mâũ hàm đươc̣ đăṭ trong các file .h mà ngườ i ta goị là các file header và để goị tớ i môṭ hàm chúng ta cần có chỉ thi ̣ #include file header tương ứ ng với hàm mà chúng ta định sử dụng . Khi đó trong quá trình biên dic̣ h trình biên dic̣ h sẽ tự tìm các hàm chuẩn (đươc̣ coi là môṭ phần cơ bản của ngôn ngữ ) còn với các hàm người dùng điṇ h nghiã chúng ta cần chỉ rõ đườ ng dâñ tớ i các file chứ a phần cài đăṭ của hàm (thường là file .cpp hoăc̣ môṭ file thư viêṇ tư ̣ taọ nào đó). 1.2 Hàm và các biến Các biến nằm trong hàm cũng như các biến là tham số được truyền cùng với một hàm được gọi là các biến cục bộ của một hàm . Các biến nằm ngoài các hàm được gọi là các biến toàn cuc̣ . 1.3 Truyền tham số Viêc̣ truyền tham số cho môṭ hàm có thể đƣơc̣ tiến hành theo 3 hình thức khác nhau: Truyền theo giá tri ̣ Đây là cách truyền các tham số măc̣ điṇ h của C và C ++. Các biến được truyền theo cách này thực sư ̣ là các biến cuc̣ bô ̣của hàm . Khi hàm đươc̣ goị thưc̣ hiêṇ se ̃ xuất hiêṇ bản copy của các biến này và hàm se ̃ làm viêc̣ vớ i chúng . Sau khi hàm đươc̣ thưc̣ hiêṇ xong các bản copy này sẽ được loại khỏi bộ nhớ của chương trình. Do hàm làm viêc̣ vớ i các bản copy của các tham số nên các biến truyển theo giá tri ̣không bi ̣ảnh hưở ng . Truyền theo điạ chi ̉ Viêc̣ truyền theo điạ chỉ đươc̣ thưc̣ hiêṇ khi chúng ta muốn thay đổi các biến đươc̣ truyền cho môṭ hàm . Thay vì làm viêc̣ vớ i các bản copy của tham số trong trườ ng hơp̣ truyền theo điạ chỉ hàm se ̃ thao tác trưc̣ tiếp trên các biến đươc̣ truyền vào thông qua điạ chỉ của chúng. Ngoài mục đích là làm thay đổi các biến ng oài việc truyền theo địa chỉ còn 34
  42. đươc̣ thưc̣ hiêṇ khi chúng ta truyền môṭ biến có kích thướ c lớ n (môṭ mảng chẳng haṇ ), khi đó viêc̣ truyền bằng điạ chỉ se ̃ tiết kiêṃ đươc̣ không gian nhớ cần sử duṇ g . Tuy nhiên viêc̣ truyền tham số theo điạ chỉ đôi khi gây ra rất nhiều lỗi khóa kiểm soát. Tham số là mảng môṭ chiều : trong trườ ng hơp̣ tham số của môṭ hàm là mảng môṭ chiều chúng ta cần dùng thêm môṭ biến kiểu int để chỉ điṇ h số phần tử của mảng, ví dụ: void sort(int * a, int); // hay void sort(int a[], int); Tham số là mảng hai chiều: vớ i mảng hai chiều chúng ta có thể sử duṇ g hai cách sau đây để thưc̣ hiêṇ các thao tác: void readData(int a[][5], int m. int n); hoăc̣ void readData(int *a, int m. int n){ for (int i=0;i > *(a+m*i+j); return; } Khi đó chúng ta có thể goị hàm này như sau: int* a_ptr = new int [n*m] ; readdata(a_ptr,n,m); hoăc̣ int a[2][2]; readdata(&a[0][0],n,m); Truyền theo tham chiếu Viêc̣ thay đổi các biến ngoài cũng có thể đươc̣ thưc̣ hiêṇ bằng cách truyền theo tham chiếu. So vớ i cách truyền theo điạ chỉ truyền theo tham số an toàn hơn và do đó cũng kém linh hoaṭ hơn. Ví dụ: void swap(int &a, int &b); 1.4 Chồng hàm (overload) và tham số mặc định của hàm Chồng hàm (overload) C++ cho phép lâp̣ trình viên có khả năng viết các hàm có tên giống nhau , khả năng này được gọi là chồng hàm (overload hoăc̣ polymorphism function mean many formed).Ví dụ chúng ta có thể có các hàm như sau: int myFunction(int); 35
  43. int myFunction(int, int); int myFunction(int, int, int); Các hàm overload cần thoả mãn một điều kiện là danh sách các tham số của chúng phải khác nhau (về số lươṇ g tham số và kiểu tham số ). Kiểu các hàm overload có thể giống nhau hoăc̣ khác nhau . Danh sách kiểu các tham số của môṭ hàm đươc̣ goị là chữ ký (signature) của hàm đó. Có sự tương tự khi sử dụng chồng hàm và các tham số mặc định và sự lựa chọn sử dụng tuỳ thuộc vào kinh nghiệm của lập trình viên . Vớ i các hàm lớ n và phứ c tap̣ chúng ta nên sử duṇ g chồng hàm , ngoài ra việc sử dụng các hàm chồng nhau cũng làm cho chương trình sáng sủa và dê ̃ gỡ lỗi hơn. Chú ý là không thể overload các hàm static. Các tham số mặc định Các biến được truyền làm tham số khi thực hiện gọi một hàm phải có kiểu đúng như nó đã được khai báo trong phần prototype của hàm. Chỉ có một trường hợp khác đó là khi chúng ta khai báo hàm với tham số có giá trị mặc định ví dụ: int myFunction(int x=10); Khi đó khi chúng ta thưc̣ hiêṇ goị hàm và không truyền tham số , giá trị 10 sẽ được dùng trong thân hàm. Vì trong prototype không cần tên biến nên chúng ta có thể thực hiện khai báo như sau: int myFunction(int =10); Trong phần cài đăṭ của hàm chúng ta vâñ tiến hành bình thườ ng như các hàm khác: int myFunction(int x){ } Bất kỳ một tham số nào cũng có thể gán các giá trị mặc định chỉ có một hạn chế : nếu môṭ tham số nào đó không đươc̣ gán các giá tri ̣măc̣ điṇ h thì các tham số đứ ng trướ c nó cũng không thể sử dụng các giá trị mặc định. Ví dụ với hàm: int myFunction(int p1, int p2, int p3); Nếu p3 không đươc̣ gán các giá tri ̣măc̣ điṇ h thì cũng không thể gán cho p 2 các giá trị mặc định. Các giá trị mặc định của tham số hàm thường được sử dụng trong các hà m cấu tử của các lớp. 1.5 Các vấn đề khác Hàm inline Các hàm inline được xác định bằng từ khóa inline. Ví dụ: inline myFunction(int); 36
  44. Khi chúng ta sử các hàm trong môṭ chương trình C hoăc̣ C ++ thườ ng thì phần thân hàm sau khi đươc̣ biên dic̣ h se ̃ là môṭ tâp̣ các lêṇ h máy . Mỗi khi chương trình goị tớ i hàm , đoaṇ ma ̃ của hàm se ̃ đươc̣ nap̣ vào stack để thưc̣ hiêṇ sau đó trả về 1 giá trị nào đó nếu có và thân hàm được loại khỏi stack thực hiện của chương trình. Nếu hàm đươc̣ goị 10 lần se ̃ có 10 lêṇ h nhảy tương ứ ng vớ i 10 lần nap̣ thân hàm để thưc̣ hiêṇ . Vớ i chỉ thi ̣inline chúng ta muốn gơị ý cho trình biên dic̣ h là thay vì nap̣ thân hàm như bình thườ ng haỹ chèn đoaṇ mã của hàm vào đúng chỗ mà nó được gọi tới trong chương trình . Điều này rõ ràng làm cho chương trình thưc̣ hiêṇ nhanh hơn bình thườ ng . Tuy nhiên inline chỉ là môṭ gơị ý và không phải bao giờ cũng đươc̣ thưc̣ hiêṇ . Vớ i các hà m phứ c tap̣ (chẳng haṇ như có vòng lăp̣ ) thì không nên dùng inline . Các hàm inline do đó thường rất gắn chẳng hạn như các hàm chỉ thực hiện một vài thao tác khởi tạo các biến (các hàm cấu tử của các lớp ). Với các lớ p khi khai báo các hàm inline chúng ta có thể không cần dùng từ khóa inline mà thưc̣ hiêṇ cài đăṭ ngay sau khi khai báo là đủ . Hàm đệ qui Đê ̣qui là môṭ cơ chế cho phép môṭ hàm có thể goị tớ i chính nó . Kỹ thuật đệ qui thườ ng gắn vớ i các vấn đề mang tính đê ̣qui hoăc̣ đươc̣ xác điṇ h đê ̣qui . Để giải quyết các bài toán có các chu trình lồng nhau người ta thường dùng đệ qui . Ví dụ như bài toán tính giai thừ a, bài toán sinh các hoán vị của n phần tử Sƣ̉ duṇ g tƣ̀ khó a const Đôi khi chúng ta muốn truyền môṭ tham số theo điạ chỉ nhưng không muốn thay đổi tham số đó , để tránh các lỗi có thể xảy ra chúng ta có thể sử dụng từ khóa const . Khi đó nếu trong thân hàm chúng ta vô ý thay đổi nôị dung của biến trình biên dic̣ h se ̃ báo lỗi . Ngoài ra việc sử dụng từ khóa const còn mang nhiều ý nghĩa khác liên quan tới các phương thứ c của lớ p (chúng ta sẽ học trong chương 5). 2. Con trỏ, hàm và mảng Môṭ số khái niêṃ về con trỏ cũng đa ̃ đươc̣ nhắc đến trong chương 2 nên không nhắc lại ở đây chúng ta chỉ chú ý một số vấn đề sau: Cấp phát bô ̣nhớ cho biến con trỏ bằng toán tủ new: int *p = new int[2]; và xóa bỏ nó bằng toán tử delete. Phân biêṭ khai báo int a[]; và int *p; Trong trườ ng hơp̣ thứ nhất chúng ta có thể thưc̣ hiêṇ khở i taọ các gía tri ̣cho mảng a còn trong trường hợp thứ hai thì không thể. Cần nhớ rằng tên của mảng chính là con trỏ trỏ tới phần tử đầu tiên của mảng do đó viêc̣ gán: int *pa = &a[0]; và pa = a; là như nhau. Cũng do trong C và C ++ không kiểm soát số phần tử của môṭ mảng nên để truyền môṭ mảng cho hàm chúng ta thườ ng dùng thêm các biến nguy ên chỉ số lươṇ g phần tử của mảng (xem laị ví du ̣phần truyền biến). 37