Giáo trình Lập trình hướng đối tượng C++ - Cao đẳng Công nghiệp Huế

pdf 197 trang ngocly 2590
Bạn đang xem 20 trang mẫu của tài liệu "Giáo trình Lập trình hướng đối tượng C++ - Cao đẳng Công nghiệp Huế", để 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:

  • pdfgiao_trinh_lap_trinh_huong_doi_tuong_c_cao_dang_cong_nghiep.pdf

Nội dung text: Giáo trình Lập trình hướng đối tượng C++ - Cao đẳng Công nghiệp Huế

  1. Khoa Công nghệ Thông tin Trường Cao đẳng Công nghiệp Huế GIÁO TRÌNH LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG C++ (Lưu hành nội bộ) Huế, tháng 06/2011
  2. PHỤ LỤC GIỚI THIỆU 7 MÔI TRƯỜNG PHÁT TRIỂN TÍCH HỢP IDE 10 CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ 22 1.1. Cấu trúc của một chương trình C++ 22 1.2. Biến và các kiểu dữ liệu 25 1.2.1.Từ khóa 25 1.2.2. Kiểu dữ liệu nguyên thủy 26 1.2.3. Khai báo biến 26 1.2.4. Phạm vi tác dụng của biến 27 1.2.5. Khởi tạo giá trị cho biến 29 1.2.6. Khởi tạo giá trị cho biến tĩnh static 29 1.2.7. Giới thiệu về xâu kí tự 30 1.3. Hằng 31 1.3.1. Hằng số nguyên 31 1.3.2. Hằng số thực có dấu chấm động 31 1.3.3. Hằng kí tự và hằng xâu kí tự 32 1.3.4. Hằng logic 33 1.3.5. Định nghĩa một hằng #define 33 1.3.6. Khai báo hằng const 34 1.4. Toán tử 34 1.4.1. Toán tử gán 34 1.4.2. Toán tử thực hiện phép toán số học 35 1.4.3. Toán tử gán hợp nhất 36 1.4.4. Toán tử tăng và giảm 36 1.4.5. Toán tử so sánh 37 1.4.6. Toán tử logic 38 1.4.7. Toán tử điều kiện 39 1.4.8. Toán tử phân tách 40 1.4.9. Toán tử dịch bit 41 C++ T r a n g | 2
  3. 1.4.10. Toán tử chuyển đổi kiểu dữ liệu 43 1.4.11. Các toán tử khác 43 1.4.12. Thứ tự ưu tiên của các toán tử 43 1.5. Xuất – nhập cơ bản 45 1.5.1. Xuất dữ liệu chuẩn cout 45 1.5.2. Nhập dữ liệu chuẩn cin 46 1.5.3. Nhập dữ liệu nhờ lớp stringstream 48 1.6. Các cấu trúc lệnh điều khiển 49 1.6.1. Cấu trúc lệnh có điều kiện: if và else 50 1.6.2. Cấu trúc lặp 52 1.6.3. Cấu trúc lựa chọn: switch 57 1.7. Hàm 61 1.7.1. Khai báo và sử dụng hàm 62 1.7.2. Phạm vi tác dụng của biến 65 1.7.3. Hàm không trả về giá trị - Hàm void. 66 1.7.4. Tham biến và tham trị 67 1.7.5. Giá trị mặc định của tham số hình thức 70 1.7.6. Chồng chất hàm 71 1.7.6. Hàm nội tuyến 72 1.7.7. Hàm đệ quy 72 1.8. Các kiểu dữ liệu có cấu trúc 74 1.8.1. Mảng 74 1.8.2. Xâu kí tự 78 1.9. Con trỏ 78 1.9.1. Toán tử tham chiếu & 79 1.9.2. Toán tử tham chiếu ngược * 80 1.9.3. Khai báo biến con trỏ 81 1.9.4. Con trỏ, mảng và xâu kí tự 83 1.9.5. Các phép toán số học trên con trỏ 85 1.9.6. Con trỏ trỏ vào con trỏ 87 1.9.7. Con trỏ void 88 C++ T r a n g | 3
  4. 1.9.8. Con trỏ null 89 1.9.9. Con trỏ hàm 89 1.10. Bộ nhớ động 91 1.10.1. Toán tử new và new[] 91 1.10.2. Toán tử delete và delete[] 92 1.11. Kiểu dữ liệu struct và Con trỏ struct 93 1.11.1. Struct 93 1.11.2. Con trỏ struct 97 1.11.3. Struct lồng nhau 98 1.11.4. Kích thước bộ nhớ của struct 98 1.12. Các kiểu dữ liệu khác 99 1.12.1. Kiểu dữ liệu tự định nghĩa 99 1.12.2. Kiểu dữ liệu union thường 100 1.12.3. Kiểu dữ liệu union ẩn danh 100 1.12.4. Kiểu dữ liệu enum 101 CHƯƠNG 2. LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG 102 2.1. Lịch sử hình thành 102 2.2. Lớp và đối tượng 107 2.3. Hàm tạo và hàm hủy 110 2.4. Chồng chất hàm tạo 112 2.5. Hàm tạo sao chép 114 2.6. Tính đóng gói – Encapsulation 119 2.7. Con trỏ đối tượng 120 2.8. Lớp được khai báo nhờ từ khóa struct và union 121 2.9. Con trỏ this 121 2.10. Thành viên tĩnh – Từ khóa static 123 2.11. Hàm bạn và lớp bạn 124 2.12. Chồng chất toán tử 126 2.13. Tính kế thừa - Inheritance 132 2.14. Các mức truy cập 135 2.15. Tính đa kế thừa – Multiple Inheritance 137 C++ T r a n g | 4
  5. 2.16. Tính đa hình – Polymorphism 138 2.17. Tính trừu tượng hóa - Abstraction 149 2.18. Hàm mẫu – Template Function 149 2.19. Lớp mẫu – Template class 150 CHƯƠNG 3. NAMESPACE 154 3.1. Từ khóa namespace 154 3.2. Từ khóa using 154 3.3. Phạm vi của namespace 156 3.4. Tái định danh cho namespace 156 3.5. Namespace std 157 CHƯƠNG 4. NGOẠI LỆ 158 4.1. Câu lệnh try catch 158 4.2. Câu lệnh throw 158 4.3. Thư viện chuẩn exception 158 CHƯƠNG 5. LÀM VIỆC VỚI FILE 161 5.1. Mở file 161 5.2. Đóng file 163 5.3. File văn bản 163 5.4. Kiểm tra trạng thái của các cờ hiệu 164 5.5. Con trỏ get và put 164 5.6. File nhị phân 166 5.7. Bộ đệm và Đồng bộ hóa 167 CHƯƠNG 6. CÁC LỚP THƯ VIỆN 168 6.1. Lớp số phức complex 168 6.2. Lớp ngăn xếp stack 169 6.3. Lớp hàng đợi queue 170 6.3. Lớp vector 171 6.4. Lớp string 174 6.5. Lớp list 176 6.6. Lớp map 176 6.7. Lớp set 177 C++ T r a n g | 5
  6. 6.8. Các lớp thư viện nhập xuất 177 HƯỚNG DẪN THỰC HÀNH 184 BÀI THỰC HÀNH SỐ 1 184 BÀI THỰC HÀNH SỐ 2 184 BÀI THỰC HÀNH SỐ 3 185 BÀI THỰC HÀNH SỐ 4 186 BÀI THỰC HÀNH SỐ 5 186 BÀI THỰC HÀNH SỐ 6 187 BÀI TẬP NÂNG CAO 188 BÀI TẬP LỚN 194 DANH SÁCH HÌNH 196 TRA CỨU TỪ KHÓA 197 TÀI LIỆU THAM KHẢO 198 C++ T r a n g | 6
  7. GIỚI THIỆU Cấu trúc của giáo trình Giáo trình được chia ra làm 6 chương và mỗi chương được chia làm các mục khác nhau. Các chương được sắp xếp theo trình tự từ lập trình hướng thủ tục trên C++ đến lập trình hướng đối tượng và các lớp thư viện cơ bản. Độc giả có thể truy cập vào mục bất kì từ phần phụ lục nằm đầu sách. Nhiều mục bao gồm các ví dụ để mô tả cách sử dụng. Tôi khuyên các bạn nên đọc các ví dụ này và có thể hiểu mỗi đoạn mã chương trình trước khi đọc chương tiếp theo. Một cách thức tốt để tăng lượng kiến thức nhận được đó là hãy chỉnh sửa, bổ sung mã lệnh mới dựa trên ví dụ mẫu, theo hướng tư duy của của bản thân, để từ đó có thể hiểu một cách đầy đủ về nội dung mà ta tiếp thu được. Sau khi đọc xong giáo trình, tôi còn cung cấp một số bài tập thực hành đề nghị để độc giả nên thử nghiệm. Hãy giải những bài tập này, chúng sẽ rất hữu ích và giúp các bạn cũng cố lại kiến thức môn học cũng như hiểu sâu sắc hơn phần lý thuyết. Một điều nữa mà độc giả cần lưu ý: hãy đọc trang cuối cùng của cuốn sách, để nắm được một số thuật ngữ anh-việt tương ứng được sử dụng trong giáo trình này. Tôi cũng có gắng sử dụng tên gọi phù hợp nhất với đại đa số các giáo trình hiện hành. Tuy nhiên, độc giả cũng nên nắm các thuật ngữ tiếng anh tương ứng, để có thể tham khảo thêm các tài liệu chuyên môn tiếng anh. Khi biên soạn giáo trình này, tôi không thể tránh khỏi sai sót. Rất mong sự đóng góp ý kiến quý báu của các bạn độc giả cũng như các bạn đồng nghiệp. Mọi sự đóng góp xin liên hệ theo địa chỉ email: dnhthanh@hueic.edu.vn hoặc myhoangthanh@yahoo.com . Hi vọng với các ý kiến đóng góp của các bạn, giáo trình này sẽ ngày càng hoàn thiện hơn. Một vài chú ý về sự tương thích của C và C++ Chuẩn ANSI-C++ được một tổ chức tiêu chuẩn quốc tế thống nhất đưa ra. Nó được chính thức ra mắt vào tháng 11 năm 1997 và duyệt lại vào năm 2003. Tuy nhiên, ngôn ngữ C++ đã tồn tại trước đó một thời gian khá dài (vào năm 1980). Trước đó, có rất nhiều trình dịch không hỗ trợ các tính năng mới bao gồm cả chuẩn ANSI-C++. Giáo trình này được xây dựng trên các chương trình dịch hiện đại hỗ trợ đầy đủ chuẩn ANSI-C++. Tôi đảm bảo rằng các ví dụ sẽ hoạt động tốt nếu độc giả sử dụng một trình dịch hỗ trợ ANSI- C++. Có nhiều sự chọn lựa, có thể là miễn phí hoặc các phần mềm thương mại. Trong giáo trình này, tôi giới thiệu đến các các bạn hai công cụ biên dịch C++ là GCC MinGW – miễn phí và Visual C++ - thương mại. C++ T r a n g | 7
  8. Trình biên dịch Các ví dụ trong cuốn giáo trình này được xây dựng chủ yếu trên chế độ console (màn hình DOS). Điều đó có nghĩa là nó sử dụng chế độ văn bản để hiển thị các kết quả. Mọi trình dịch C++ đều hỗ trợ chế độ dịch console. Với một môi trường phát triển tích hợp IDE cho C++ miễn phí, chúng ta có thể sử dụng chương trình Codeblocks hoặc Eclipse. Chúng là các môi trường phát triển tích hợp hỗ trợ soạn thảo và biên dịch C++. Chúng hỗ trợ môi trường GCC để biên dịch cả C và C++. Với CodeBlocks, chúng ta có thể tải phần mềm tại địa chỉ bên dưới1. Đối với Eclipse, nó là một trình soạn thảo và biên dịch ngôn ngữ lập trình chuyên nghiệp nhưng hoàn toàn miễn phí (vì ta có thể cấu hình kết hợp với các công cụ biên dịch khác nhau để tạo ra môi trường phát triển tích hợp cho các ngôn ngữ lập trình khác nhau). Chúng ta có thể dùng nó để soạn thảo và biên dịch Java, PHP, JSP, Python và hiển nhiên là cả C/C++. Đây là một dự án mã nguồn mở, tiêu tốn hàng triệu đôla của IBM. Để tải về bản mới nhất cho đến thời điểm này (năm 2010) là Eclipse Helios, ta có thể truy cập đến địa chỉ bên dưới2. Đối với Eclipse, chúng ta nên sử dụng kết hợp với trình biên dịch C++ là MinGW, nó cũng là một dự án mở. Chúng ta có thể tải về tại địa chỉ bên dưới3. Với Eclipse, thì công việc cấu hình ban đầu tương đối phức tạp. Nhưng nó là một trình soạn thảo tuyệt vời. Ta có thể sử dụng nó để soạn thảo nhiều ngôn ngữ lập trình bằng cách cài đặt thêm plugin hỗ trợ. Nhiều nhà phát triển đã sử dụng Eclipse làm nền tảng cho việc phát triển các ứng dụng của mình: Embarcadero sử dụng nó để phát triển JBuider, Adobe sử dụng nó để phát triển Flash Buider và rất nhiều các hãng phần mềm nổi tiếng khác. Nếu là một lập trình viên Java, Eclipse là một sự lựa chọn không thể bỏ qua. Nếu phát triển Flash theo dự án mã nguồn mở từ Adobe, Eclipse cũng là sự lựa chọn hoàn hảo. Nếu phát triển C/C++, với các trình soạn thảo thì Eclipse cũng là sự lựa chọn không tồi. Việc sử dụng thành thạo Eclipse sẽ là một lợi thế cho chúng ta khi tiến hành nghiên cứu Java, lập trình Web, Flex, Python sau này. Bên cạnh đó, chúng tôi cũng giới thiệu môi trường phát triển tích hợp IDE Microsoft Visual Studio. Đây là trình biên dịch thương mại và là trình biên dịch chuyên nghiệp và nổi tiếng nhất trên hệ điều hành Windows. Ta có thể sử dụng để phát triển các ứng dụng trên nền NET hoặc các ứng dụng Win32. Nếu muốn phát triển các ứng dụng theo hướng của Microsoft, ta nên sử dụng Visual Studio. Phiên bản mới nhất đến thời điểm này là VS 2010. Nhưng cần lưu ý rằng, khi nghiên cứu Visual C++, hãy chọn lựa phiên bản dành cho Win32 mà không phải là ứng dụng CLI (common language infrastructure) 1 2 win32.zip 3 inst/mingw-get-inst-20100831/mingw-get-inst-20100831.exe C++ T r a n g | 8
  9. bởi nó được phát triển trên nền NET. Và Visual C++ for NET có một số khác biệt so với Visual C++ for Win32. C++ T r a n g | 9
  10. MÔI TRƯỜNG PHÁT TRIỂN TÍCH HỢP IDE a) CodeBlocks Trước tiên, chúng ta sẽ tìm hiểu cách tạo dự án, biên dịch một tập tin C++ trên CodeBlocks. Độc giả cũng cần lưu ý rằng, CodeBlocks tổ chức công việc theo các dự án. Chúng ta có thể biên dịch từng tập tin cpp một cách đơn lẻ. Tuy nhiên, làm việc theo dự án sẽ giúp ích cho chúng ta rất nhiều khi làm việc với những tác vụ lớn. Đầu tiên chúng ta khởi động codeblocks, sau đó vào File > New > Project. Trong hộp thoại hiện ra, chúng ta chọn console application (Hình 1). Và nhấp Go, sau đó nhấp Next. Trong hộp thoại tiếp theo, ta chọn C++ và nhấp Next. Hình 1 – Tạo mới dự án trong CodeBlocks Hộp thoại yêu cầu điền thông tin về dự án sẽ xuất hiện. Hãy điền tên dự án, vị trí lưu trữ dự án. Sau đó nhấp Next. Cuối cùng nhấp Finish. Trong cửa sổ quản lý dự án, ta nhấp đôi chuột vào tệp main.cpp. Nội dung soạn thảo sẽ được nhập vào trong tập tin này. Nếu ta muốn bổ sung các tập tin khác hoặc các lớp đối tượng, ta có thể bổ sung chúng từ menu File > New. Biên dịch chương trình: + Nhấp vào Build > Build and Run. + Hoặc nhấp phím F9.
  11. Tự động định dạng mã. Khi viết một chương trình C++ hay bất kì một chương trình trên một ngôn ngữ lập trình nào khác, ta cần tuân thủ quy phạm định dạng mã nguồn. Có nhiều chuẩn mực cho các định dạng mã nguồn: chuẩn Hungary, chuẩn lạc đà Dù rằng, chúng không ảnh hưởng đến việc biên dịch chương trình, nhưng chúng giúp người đọc có thể dễ hiểu chương trình của chúng ta hơn. Nếu ta không nắm vững các quy phạm này thì có thể sử dụng chức năng định dạng mã nguồn tự động của CodeBlocks. Hãy kích chuột phải vào vùng soạn thảo, sau đó chọn Format this file (Astyle). Tự động khởi tạo phần thân các phương thức của lớp. Để hỗ trợ cho việc soạn thảo, CodeBlocks cũng hỗ trợ chức năng khởi tạo nhanh mã nguồn. Để khởi tạo nhanh phần khai báo thân phương thức của lớp từ khai báo prototype của nó, chúng ta đặt trỏ chuột vào sau khai báo lớp (tức vị trí sẽ chèn khai báo thân phương thức), sau đó, kích chuột phải, chọn Insert > All class methods without implementation. Trong hộp thoại hiện ra, hãy chọn những phương thức muốn khởi tạo phần thân tương ứng, sau đó, nhấp Ok. Hình 2 – Khởi tạo thân phương thức b) Eclipse Helios Sau khi tải xong Eclipe Helios về máy, hãy tiến hành giải nén tập tin. Chương trình Eclipse không yêu cầu chúng ta phải cài đặt, nhưng nó có thể làm việc nếu trên máy tính đã cài một máy ảo Java. Để tải về máy ảo Java, chúng ta có thể truy cập vào trang chủ của Sun (nay là Oracle) tại địa chỉ sau đây4. Để xây dựng một chương trình C/C++ trên Eclipse, chúng ta cần: - Eclipse Helios for C/C++ (nếu phiên bản tải về là dành cho Java, ta cần phải cài đặt thêm plugin hỗ trợ); hoặc có thể sử dụng một ấn bản cũ hơn của Eclipse như Galileo, Europa . - Công cụ biên dịch GCC – MingW. 4 C++ T r a n g | 11
  12. - Máy ảo Java JVM. Các bước cấu hình trên Eclipse Helios Bước 1. Cài đặt máy ảo Java. Bước 2. Cài MinGW. Bước 3. Giải nén Eclipse Helios, sau đó khởi động nó (nhấp vào tập tin eclipse.exe). Thông thường, Eclipse sẽ tự động cấu hình MinGW giúp ta. Nếu không, hãy thực hiện bước 4. Bước 4. Vào menu Project > Properties. Trong hộp thoại xuất hiện, hãy chọn C/C++ Build > Settings. Hình 3 – Cấu hình MinGW trong Eclipse Helios Trong thẻ Tool Settings, ta chọn GCC Assembler > General. Sau đó, nhấp vào biểu tượng có dấu cộng mầu xanh. Hộp thoại sau sẽ hiện ra: Hình 4 – Chọn đường dẫn đến thư mục bin của MinGW Ta tiến hành hãy nhập tên đường dẫn đến thư mục bin của MinGW (hoặc nhấp vào nút File system để duyệt đến thư mục này). Mặc định khi cài đặt, thư mục này sẽ là C:\MinGW\bin. Sau đó nhấp Ok. Vậy là công việc cấu hình đã hoàn tất. Xây dựng dự án đầu tiên trên Eclipse Cũng giống như CodeBlocks, Eclipse cũng tổ chức chương trình theo dự án. Để tạo mới một dự án trong Eclipse, chúng ta có ba cách: - Vào File > New > C++ Project. - Vào biểu tượng tạo mới dự án trên thanh công cụ, chọn C++ Project. - Kích chuột phải vào cửa sổ Project Explorer > chọn New > C++ Project. Tiếp đến, hộp thoại sau đây sẽ xuất hiện. C++ T r a n g | 12
  13. Trong hộp thoại này, nếu chọn một dự án khả thi (executable), hãy chọn executable. Ta cũng có thể chọn thư viện dll (static library) Tương ứng với dự án khả thi, chúng ta có thể chọn Empty Project hoặc Hello World C++ Project. Đối với Empty Project, nó sẽ tạo một dự án trống. Ngược lại với Hello World C++ Project, ta sẽ nhận được một file cpp chứa nội dung mà chúng ta sẽ thảo luận trong chương tiếp theo. Sau đó, hãy nhập vào tên dự án và nhấp Next (nếu chưa cấu hình MinGW), Hình 5 - Tạo mới dự án hoặc nhấp Finish (nếu đã hoàn tất việc cấu hình). Tạo mới một file nội dung trong Eclipse. Một chương trình trong C++ thường chia làm hai loại tệp: .cpp và .h. Tệp .cpp thường chứa nội dung chương trình, tệp .h thường chứa các khai báo. Tổ chức tập tin. Hãy tạo một thư mục chung để chứa toàn bộ nội dung sau này, tôi tạm gọi thư mục này là thư mục src. Trong thư mục src, hãy tạo hai thư mục, một thư mục cpps và một thư mục headers. Thư mục cpps sẽ chứa toàn bộ tệp .cpp, thư mục headers sẽ chứa toàn bộ tệp .h. Tệp Main.cpp chứa hàm main sẽ được đặt trong thư mục src. Nghĩa là ta sẽ có cấu trúc tương tự như sau: Hiển nhiên ta hoàn toàn không nhất thiết phải src thực hiện theo như cấu trúc thư mục này. Tuy nhiên điều này sẽ làm cho dự án của ta trở nên sáng sủa hơn rất nhiều. Chúng ta có thể cpps bổ sung thêm các thư mục phụ khác, nhưng nên tuân thủ cấu trúc cây này (ví dụ khi cần phân biệt các tập tin cpp thành nhiều loại headers khác nhau, thì trong thư mục cpps, hãy tạo thêm các thư mục con khác ) Biên dịch một dự án Main.cpp Để biên dịch một dự án, hãy nhấp vào biểu Hình 6 - Cấu trúc thư mục của một dự án tượng sau đây trên thay công cụ của Eclipse. C++ T r a n g | 13
  14. Hình 7 - Biên dịch một dự án Chọn Run As > Local C/C++ Application. Một số thủ thuật giúp soạn thảo nhanh Eclipse chứa đựng một tập các tiện ích giúp chúng ta soạn thảo nhanh hơn, ít phát sinh lỗi hơn. Sau đây, tôi xin giới thiệu một vài tính năng giúp các chúng ta soạn thảo nhanh hơn. Tạo mới một lớp Vào New > Class. Hộp thoại sau đây sẽ hiện ra Hình 8 - Hộp thoại tạo mới class Trong hộp thoại này, cần lưu ý: source folder – thư mục chứa tập tin sẽ tạo mới (thường sẽ được phân tách thành tệp .h và .cpp), namespace – phạm vi tác dụng của nó trong namespace được chỉ định, class name – tên của lớp sẽ tạo mới, base class – tên của lớp cha mà nó sẽ thừa kế (bấm vào nút add để chọn các lớp tồn tại), constructor và destructor – cho phép khởi tạo hàm tạo và hàm hủy. Chúng ta sẽ tìm hiểu những khái niệm này khi làm quen với lập trình hướng đối tượng. Tạo nhanh các phương thức Getter và Setter C++ T r a n g | 14
  15. Nếu khi khai báo một lớp, cùng với các thuộc tính của nó, thay vì sử dụng hàm tạo để thiết lập giá trị ban đầu, ta có thể dùng hàm setter; hoặc để tiếp nhận giá trị từ các thuộc tính, ta có thể dùng các hàm getter. Tôi sẽ giới thiệu chi tiết hơn về các phương thức này trong phần lập trình hướng đối tượng. Trong phần này, tôi sẽ hướng dẫn cách tạo chúng bằng thao tác nhấp chuột. Vào menu Source, chọn Generate Getters and Setters. Trong hộp thoại hiện ra, hãy chọn các thuộc tính cần tạo phương thức getter và setter, sau đó nhấp Ok. Một số phím tắt khác Phím tắt Công dụng Ctrl+Space Bật chế độ gợi nhắc lệnh. main – Ctrl+Space Khởi tạo nhanh hàm main. Ctrl+Shift+F Định dạng nhanh mã nguồn. Ctrl+/ Comment vùng mã đã được bôi đen, nếu vùng bôi đen đã ở chế độ comment, thì dấu comment sẽ bị hủy bỏ. Tab Dịch toàn bộ nội dung bị bôi đen sang phải một tab. Shift+Tab Dịch toàn bộ nội dung bị bôi đen sang trái một tab. Ctrl+1 Chỉnh sửa nhanh toàn bộ các từ giống với từ đang được bôi đen. Sau khi chỉnh sửa xong, nhấp Enter để kết thúc. Ctrl+Shift+/ Tạo một khối comment cho vùng văn bản đã bị bôi đen. Ctrl+Shift+\ Hủy bỏ vùng văn bản bị comment bởi khối comment. Trên đây, tôi đã giới thiệu sơ qua hai chương trình soạn thảo miễn phí để lập trình C/C++: CodeBlocks và Eclipse. Với CodeBlocks, chỉ cần tải và cài đặt. Môi trường hỗ trợ biên dịch GCC đã được tích hợp sẵn. Với Eclipse, ta phải thực hiện cấu hình để kết hợp với trình biên dịch GCC. Nếu là người có nhiều trải nghiệm về máy tính, thì nên chọn Eclipse bởi nó là chương trình soạn thảo rất chuyên nghiệp. Nếu là người mới tiếp xúc máy tính, hãy chọn CodeBlock vì cài đặt đơn giản. c) Visual Studio 2010 dành cho Visual C++ Visual Studio 2010 là một môi trường biên dịch tích hợp của Microsoft. Nó là trình biên dịch tốt nhất, hiện đại nhất trên hệ điều hành Windows. Chúng ta có thể sử dụng nó để biên dịch C++, C#, Visual Basic, J# Ta sẽ tìm hiểu Visual Studio theo hướng tiếp cận với C++. Một điều cần lưu ý, với phiên bản 2010 này, Visual Studio có hai phiên bản dành cho C++: C++ for Net và C++ for Win32. Chúng ta chỉ tìm hiểu về tính năng C++ for Win32. Trong nội dung của giáo trình này, ta sẽ xây dựng các ứng dụng Console trên nền Win32 mà không C++ T r a n g | 15
  16. thảo luận thêm về Visual C++ for Net bởi vì nó thuộc một phạm trù tương đối khác so với Visual C++ for Win32. Khởi động Visual Studio 2010. Để khởi động VS 2010, ta có thể thực hiện một trong hai cách sau: . Nhấp đối chuột vào biểu tượng VS 2010 trên nền Desktop. . Vào Start > All Programs > Microsoft Visual Studio 2010, chọn biểu tượng VS 2010. Hình 9 - Giao diện tổng thể của Visual Studio 2010 Tạo mới dự án trong VS 2010. Cũng như Eclipse, VS cũng quản lý theo các workspace và các dự án. Trong VS, workspace được gọi là Solution. Trong mỗi workspace có thể chứa nhiều dự án. Nếu chưa tạo một dự án nào, thì khi tạo mới một dự án, workspace sẽ tự động được tạo. Để tạo mới một dự án, ta vào File > New Project (hoặc tổ hợp phím tắt Ctrl+Shift+N). Trong hộp thoại xuất hiện, chúng ta chọn Win32 Console Application. C++ T r a n g | 16
  17. Hình 10 - Tạo dự án Win32 Console Mục name: hãy nhập tên dự án mà cần tạo. Mục Location: nhấp vào nút Browse để chọn vị trí lưu trữ. Mặc định, Visual Studio sẽ lưu trữ dự án ở thư mục Documents. Mục Solution name: tạo một thư mục con trong thư mục dự án, hay tạo trực tiếp trong thư mục dự án. Hộp thoại Hình 12 sẽ hiện ra. Nhóm Application Type Hình 11 - Win32 Application Wizard + Windows application: tạo ứng dụng winform. + Console application: tạo ứng dụng chạy trên DOS. + Dll: tạo thư viện dll. C++ T r a n g | 17
  18. + Static library: tạo thư viện tĩnh. Nhóm Add common header file + Alt: tạo header từ lớp thư viện Alt. + MFC: tạo header từ lớp thư viện MFC. Nhóm Additional options + Empty project: tạo dự án rỗng không có tập tin. + Export symbols: xuất bản các biểu tượng. + Precompiled header: tạo tập tin tiêu đề tiền biên dịch. Hãy chọn Console Application và chọn Empty Project. Sau đó, nhấp Finish. Tạo các tập tin trong dự án. Trong cửa sổ Solution Explorer, hãy kích chuột phải và chọn Add: - Nếu tập tin đã tồn tại, hãy chọn Add Existing Items. Sau đó, chúng ta duyệt đến vị trí tồn tại tập tin. Hình 12 - Bổ sung thêm một tập tin - Nếu tập tin chưa tồn tại, hãy chọn Add New Items. Trong cửa sổ xuất hiện, tùy thuộc vào tập tin mà chúng ta cần, hãy chọn loại tương ứng. Thông thường, trong dự án của C++, chúng ta sử dụng hai tập tin là tiêu đề .h và thân chương trình .cpp. Sau đó, hãy nhập tên của tập tin và nhấp Ok. Tệp tin tiêu đề .h thường chứa các khai báo prototype của hàm hoặc lớp. Ngoài ra, nó có thể chứa các hàm macro, các khai báo hằng và biến toàn cục được sử dụng trong toàn bộ chương trình. Tập tin .cpp thường chứa phần thân của các hàm hoặc lớp. Khi C++ T r a n g | 18
  19. làm việc với các dự án trong C++, chúng ta nên tách chương trình thành nhiều phần và nên sử dụng các tệp tiêu đề để làm cho chương trình gọn gàng và dễ hiểu hơn. Sau khi chọn được tập tin cần tạo, hãy nhập tên của tập tin, sau đó nhấp nút Add. Tập tin mới sẽ được bổ sung vào dự án. - Add Class: bổ sung các lớp đối tượng cho dự án. Ở đây, chúng ta chọn C++ class. Hình 13 - Bổ sung thêm lớp đối tượng Nhập Add. Cửa sổ sau đây sẽ xuất hiện Hình 14 - Tạo lớp bằng Class Wizard C++ T r a n g | 19
  20. - Class name: tên của lớp. - .h file: tên của tiêu đề lớp cũng là tên của tập tin tiêu đề. - .cpp file: tên của tập tin .cpp tương ứng với lớp. - Base class: nếu lớp mới tạo ra thừa kế từ một lớp khác, hãy nhập tên của lớp cơ sở vào đây. - Access: mức thừa kế của lớp đang tạo từ lớp cơ sở. - Virtual destructor: tạo một phương thức hủy ảo. - Inline: tạo một phương thức inline. Tuy chúng ta có thể sử dụng từ khóa này, nhưng cơ chế làm việc của Visual C++ là tự động bổ sung inline khi biên dịch nếu phương thức được cho là phù hợp để sử dụng inline. Điều đó có nghĩa là chúng ta không cần dùng đến từ khóa này. Biên dịch dự án. - Để biên dịch và thực thi một dự án, chúng ta nhấp vào Debug > Start Debugging (hoặc Start without Debugging). - Để biên dịch toàn bộ dự án mà không thực thi dự án, chúng ta vào Build, chọn Build Solution. Một số phím tắt trong Visual Studio 2010. - Tạo vùng comment (chú thích): bôi đen vùng mã cần tạo chú thích, nhấn tổ hợp Ctrl+K, Ctrl+C. - Hủy bỏ vùng comment: bôi đen vùng mã đã comment, nhấn tổ hợp Ctrl+K, Ctrl+U. - Định dạng mã nguồn: bôi đen vùng mã cần định dạng, nhấn tổ hợp Ctrl+K, Ctrl+F. - Định dạng nhanh mã nguồn: bôi đen vùng mã lệnh, nhấn tổ hợp Ctrl+K, Ctrl+F. - Tự động hoàn tất mã và gợi nhắc lệnh: tổ hợp Ctrl+Space. Visual Studio 2010 không hỗ trợ các tính năng mạnh mẽ cho việc khởi tạo nhanh mã nguồn. Nhưng ta có thể sử dụng tiện ích Visual Assist. Phiên bản cho đến thời điểm này (năm 2010) là 10.6. Xem biểu đồ lớp. Để quan sát biểu đồ lớp trong VS 2010, ta nhấp chuột phải vào tên dự án (trong cửa sổ Solution Explorer), chọn Show class diagram. Sau đó, chúng ta kéo thả các lớp đối tượng vào vùng biểu đồ. C++ T r a n g | 20
  21. Hình 15 - Xem biểu đồ lớp C++ T r a n g | 21
  22. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ 1.1. Cấu trúc của một chương trình C++ Một cách thức tốt nhất để học lập trình đó là hãy thử viết một chương trình đầu tiên. Nếu chúng ta đã từng làm quen với một ngôn ngữ lập trình nào đó, thì chắc hẳn ai cũng biết đến ví dụ kinh điển của một ngôn ngữ lập trình đó là chương trình “Hello, world !”. Mã chương trình Kết quả [1.] //my first program Hello, world ! [2.] #include [3.] using namespace std; [4.] int main() [5.] { [6.] cout . Nó chứa các hàm xuất nhập cơ bản. Hàm này là một phần của namespace std. [3.] Trong C++, các thành phần của thư viện chuẩn được khai báo trong namespace. Ở đây là namespace std. Để có thể truy xuất đến các thành phần của nó, chúng ta mô tả nó bằng từ khóa using. Trong thư viện chuẩn của C++, đối tượng cout được tổ chức trong namespace std. [4.] Bất kì một chương trình C++ nào cũng phải có một hàm main để thực thi chương trình. Một hàm sẽ được khai báo theo cấu trúc trên. Từ khóa int mô tả kiểu dữ liệu mà hàm trả về là integer. Chúng ta cần lưu ý rằng, trong chương trình C thì ta có thể tùy ý khai báo là void hoặc int, nhưng trong C++ thì bắt buộc phải khai báo là int. Vậy int hay void trong trường hợp này có thực sự quan trọng ? Chúng ta nên luôn khai báo hàm main có kiểu dữ liệu trả về là kiểu int. Sở dĩ như vậy là vì khi hàm main trả về kiểu int thì theo quy ước, nếu chương trình có lỗi, nó sẽ trả về một mã int khác 0 và ngược lại, nếu chương trình không có lỗi, nó sẽ trả về mã int 0. Lỗi ở đây là lỗi chương trình liên quan đến quá trình biên dịch, chứ không phải là lỗi
  23. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ liên quan đến cú pháp. Chúng ta sẽ nhận thấy mã mà nó trả về trong dòng thông báo cuối cùng khi biên dịch: process returned 0 (0x0). Tên hàm là main. Tiếp theo là cặp dấu ngoặc đơn dùng để chứa tham số đính kèm. Thông thường một chương trình ứng dụng sẽ chứa hai tham số trong hàm main là int argc và char* args[]. Các tham số này gọi là tham số dòng lệnh. Tiếp theo là dấu {}. Bên trong cặp dấu này là chương trình chính. [5.] Dấu mở khối. [6.] Đối tượng cout (đọc là C-out) là chuẩn dùng để xuất dữ liệu ra màn hình. Chúng ta cần lưu ý hàm printf vẫn hoạt động tốt trong trường hợp này. Nếu dùng hàm printf thì ta không cần khai báo thư viện iostream và namespace std ở trên. Khi sử dụng đối tượng cout, chúng ta cũng có thể bỏ qua dòng lệnh [3.] và thay vào đó ta sẽ viết std::cout. Khi sử dụng đối tượng cout, chúng ta có thêm một cách thức để xuống dòng thay vì dùng \n, đó là endl. Đối tượng cout thường đi với toán tử xuất <<. Chúng ta có thể sử dùng nhiều toán tử này khi muốn xuất nhiều phần tử riêng biệt: cout<<string1<<string2<< .<<endl. [7.] Câu lệnh return dùng để trả về giá trị của hàm main. Nếu hàm có trả về giá trị, thì cần return một giá trị nào đó cùng kiểu dữ liệu trả về với hàm. Nếu hàm là void, thì không cần return. [8.] Dấu đóng khối tương ứng với mở khối [5]. Chú ý: . Cũng như C, C++ là ngôn ngữ phân biệt chữ hoa và chữ thường. . Kết thúc một dòng lệnh trong C++ bao giờ cũng phải có dấu ; . Một dấu ngoặc đơn (), dấu ngoặc nhọn {} bao giờ cũng song hành. Điều đó có nghĩa nếu dùng dấu mở thì phải có dấu đóng tương ứng. Dấu ngoặc đơn thường dùng sau tên hàm, và bên trong nó là tham số hình thức hoặc trong các lệnh có cấu trúc. Dấu ngoặc nhọn thường dùng để quy định phạm vi của một khối lệnh (scope). Một cách thức giúp chúng ta chuyên nghiệp hơn khi lập trình, là sau dấu mở, ta nên sử dụng tiếp dấu đóng (thông thường các trình soạn thảo sẽ hỗ trợ một cách tự động). Sau đó hãy nhập nội dung cần thiết vào bên trong cặp dấu này. Điều đó sẽ tránh khỏi sự nhầm lẫn khi chương trình có quá nhiều dấu đóng mở. . Để nhận biết được phạm vi ảnh hưởng của các khối lệnh – hãy sử dụng phím tab để tạo ra sự lồi lõm khi viết mã chương trình. Như trong ví dụ trên, đối tượng cout và hàm return sẽ nhảy vào một tab so với dấu khối lệnh tương ứng. Đừng bao giờ tiết kiệm sử dụng phím tab và phím enter. Nếu sử dụng hợp lí, chương trình sẽ rất sáng sủa và dễ đọc. Bài tập 1. 1. Hãy viết chương trình in ra dòng chữ “Chao ban, ban co khoe khong”. 2. Hãy viết chương trình in ra hai dòng chữ trên hai dòng phân biệt “Vietnam” và “Hoa ky”. 3. Hãy viết chương trình in ra tam giác đều với các đỉnh là các dấu *. C++ T r a n g | 23
  24. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ 1.2. Biến và các kiểu dữ liệu Tương ứng với chương trình “Hello world”, chúng ta cần thảo luận một vài chi tiết. Chúng ta có một vài dòng lệnh, biên dịch chúng và sau đó chạy chương trình để thu kết quả. Dĩ nhiên ta có thể làm nhanh hơn, tuy nhiên việc lập trình không chỉ đơn thuần là in ra các dòng thông báo đơn giản lên màn hình. Để đi xa hơn, chúng ta sẽ viết một chương trình thực thi một tác vụ hữu ích là giúp chúng ta tìm hiểu về khái niệm biến. Giả sử có hai giá trị 5 và 2. Ta cần lưu hai giá trị này vào bộ nhớ. Bây giờ, nếu tôi muốn cộng thêm 1 vào số thứ nhất và lưu lại giá trị này cho nó, tiếp theo tôi muốn lấy hiệu của số thứ nhất sau khi thay đổi với số thứ hai. Tiến trình xử lý công việc trên có thể được viết trên C++ như sau: Chương trình int a = 5; int b = 2; a = a + 1; // a=6 int result = a – b; //result = 4 Biến được dùng để lưu giá trị và nó có thể thay đổi được. Một biến sẽ được quy định bởi một kiểu dữ liệu nào đó. Trong trường hợp ví dụ của chúng ta, biến có kiểu dữ liệu là int. Kiểu dữ liệu thường có hai loại: kiểu dữ liệu nguyên thủy (primitive data type) và kiểu dữ liệu tham chiếu (reference data type). Chúng ta sẽ thảo luận chi tiết về chúng trong phần tiếp theo. Nhưng ta có thể hiểu rằng, một kiểu dữ liệu đơn giản và có cấu trúc trong C là kiểu dữ liệu nguyên thủy. Đối với kiểu dữ liệu tham chiếu, tôi sẽ giới thiệu trong phần lập trình hướng đối tượng trong C++. 1.2.1.Từ khóa Từ khóa trong C++ có thể có một hoặc nhiều từ. Nếu từ khóa có nhiều từ, thì giữa các từ có dấu gạch chân (_). Kí tự trắng và các kí tự đặc biệt không được phép sử dụng trong từ khóa, tên hàm, tên biến. Tên của chúng không được bắt đầu bằng kí tự số. Bảng từ khóa chuẩn trong C++ asm, auto, bool, break, case, catch, char, class, const, const_cast, continue, default, delete, do, double, dynamic_cast, else, enum, explicit, export, extern, false, float, for, friend, goto, if, inline, int ,long, mutable, namespace, new, operator, private, protected, public, register, reinterpret_cast, return, short, signed, sizeof, static, static_cast, struct, switch, template, this, throw, true, try, typedef, typeid, typename, union, unsigned, using, virtual, void, volatile, wchar_t, while
  25. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ Bảng từ khóa bổ sung trong C++ and, and_eq, bitand, bitor, compl, not, not_eq, or, or_eq, xor, xor_eq 1.2.2. Kiểu dữ liệu nguyên thủy Khi lập trình, chúng ta lưu các biến trong bộ nhớ máy tính, nhưng máy tính cần phải biết loại dữ liệu mà chúng ta muốn lưu. Điều này giúp bảo đảm đủ số lượng ô nhớ cần thiết để lưu dữ liệu. Trong máy tính, bộ nhớ được tổ chức theo các byte. Một byte là một đơn vị đo lường tối thiểu mà chúng ta có thể quản lý trong C++. Một byte có thể lưu một biến char. Thêm vào đó, máy tính cũng quản lý những kiểu dữ liệu phức tạp hơn. Bảng sau đây liệt kê các kiểu dữ liệu và kích thước tương ứng. Tên Mô tả Kích thước Vùng giá trị char Kí tự hoặc số nguyên bé 1 byte signed: -128 ->127 unsigned: 0 -> 255 short Số nguyên ngắn 2 byte signed: -215 -> 215-1 unsigned: 0 -> 216-1 int Số nguyên 4 byte signed: -231 -> 231-1 unsigned: 0 -> 232-1 long Số nguyên dài 4 byte signed: -231 -> 231-1 unsigned: 0 -> 232-1 long long Số nguyên cực dài 8 byte signed: -263 -> 263-1 unsigned: 0 -> 264-1 bool Giá trị logic – true/false 1 byte true và false float Số thập phân 4 byte 7 số thập phân double Số thập phân chấm động 8 byte 15 số thập phân long Số thập phân chấm động dài 8 byte 15 số thập phân double wchar_t Kí tự dài 2/4 byte Kích thước trong bộ nhớ và miền giá trị của các kiểu dữ liệu còn phụ thuộc vào hệ thống và chương trình dịch tương ứng. Giá trị được đưa ra ở đây là trên hệ thống Windows 32 bit và trình dịch GCC MinGW. Nhưng đối với hệ thống khác, các giá trị này có thể thay đổi (ví dụ kiểu int và long trên Windows 32 bit và 64 bit là 4 byte, nhưng trên Linux 32 bit là 4 byte và trên Linux 64 bit là 8 byte). 1.2.3. Khai báo biến Như ví dụ trên, ta thấy rằng, muốn sử dụng một biến trong C++, ta cần khai báo biến với kiểu dữ liệu mà ta mong muốn. Cấu trúc khai báo ; C++ T r a n g | 26
  26. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ Ví dụ int a; //Khai báo biến a kiểu nguyên float mynumber; //Khai báo biến mynumber kiểu float bool istrue; //Khai báo biến istrue kiểu bool long num1, num2, num3; //Khai báo ba biến num1, num2, num3 cùng kiểu long Chú ý: . Nếu khi khai báo biến thuộc các kiểu nguyên mà ta không sử dụng khai báo có dấu (signed) hoặc không dấu (unsigned), thì chương trình dịch mặc định sẽ quy định là kiểu nguyên có dấu. int mynum; //tương đương signed int mynum; . Đối với kiểu char thì có ngoại lệ. Chúng ta nên khai báo tường minh là signed char hoặc unsigned char. . Đối với signed int và unsigned int có thể viết đơn giản là signed hoặc unsigned. . Nếu muốn chắc chắn về kích thước của kiểu dữ liệu mà ta cần sử dụng, hãy sử dụng hàm sizeof để xác định kích thước bộ nhớ của kiểu dữ liệu. Hàm sizeof(tên biến) hoặc sizeof(kiểu dữ liệu) – trả về kiểu dữ liệu nguyên. Chương trình Kết quả #include 4 (trên windows 32 bit) using namespace std; int main() { int a; cout<<sizeof(a); //Hoặc có thể viết cout<<sizeof(int); } 1.2.4. Phạm vi tác dụng của biến Tất cả các biến mà tôi giới thiệu ở đây được sử dụng trong chương trình cần phải được khai báo với kiểu dữ liệu được chỉ định. Một biến được khai báo C++ T r a n g | 27
  27. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ trong một khối lệnh nào, thì nó chỉ có tác dụng trong khối lệnh đó. Biến được khai báo theo kiểu này gọi là biến cục bộ (hoặc biến địa phương). Nếu một biến được khai báo ngoài tất cả các khối lệnh (kể cả hàm main) thì biến đó có tác dụng trong toàn bộ chương trình và gọi là biến toàn cục. Chương trình [1.] #include [2.] using namespace std; [3.] int a; [4.] char c; [5.] unsigned int d; [6.] int main() [7.] {//Khối lệnh 1 [8.] signed long m; [9.] float n; [10.] {//Khối lệnh 2 [11.] double x; [12.] x = 1; [13.] cout<<x; [14.] } [15.] } Giải thích: Các biến khai báo ở các dòng [3.], [4.] và [5.] được khai báo ngoài mọi khối lệnh, nó có tác dụng trong toàn bộ chương trình và nó được gọi là biến toàn cục (global variable). Các biến được khai báo trong khối lệnh 1 (tương ứng [8.] và [9.]) và khối lệnh 2 (tương ứng [11.] và [12.]) gọi là biến cục bộ (local variable), nó có tác dụng trong khối lệnh trực tiếp chứa nó. Có nghĩa là biến x chỉ có tác dụng trong khối lệnh 2; biến m, n có tác dụng trong khối lệnh 1. Các biến toàn cục có thể được sử dụng trong toàn bộ chương trình, nó có thể được gọi trong các hàm, trong hàm chính main. Còn biến cục bộ được khai báo trong khối lệnh nào, thì nó chỉ có thể được sử dụng trong khối lệnh đó. Trong một số tình huống, biến có thể được khai báo trong dấu ngoặc đơn (tình huống này thường gặp khi nghiên cứu các lệnh có cấu trúc), thì biến này cũng gọi là biến cục bộ for (int a = 0; i<10; i++){ nhập nội dung . } Lúc này, biến sẽ có tác dụng trong khối lệnh tương ứng (khối lệnh nằm trong vòng lặp for). C++ T r a n g | 28
  28. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ 1.2.5. Khởi tạo giá trị cho biến Khi một biến cục bộ được khai báo, giá trị mặc định của nó sẽ chưa được tạo ra. Vì vậy, muốn sử dụng được biến, ta cần phải khởi tạo giá trị cho biến. Có hai cách để khởi tạo giá trị của biến trong C++. Cú pháp Ví dụ type tên_biến = giá_trị_khởi_tạo; int a = 0; type tên_biến (giá_trị_khởi_tạo); int a (0); Bây giờ, ta có thể viết lại đoạn chương trình tính toán giá trị của các biến ở trên, bằng cách sử dụng giá trị khởi tạo mặc định này. Khởi tạo theo cách 1 Khởi tạo theo cách 2 #include #include using namespace std; using namespace std; int main() int main() { { int a = 5; int a (5); int b = 2; int b (2); a = a + 1; // a=6 a = a + 1; // a=6 int result = a – b; //result = 4 int result (a – b); //result = 4 cout<<result<<endl; cout<<result<<endl; result 0; result 0; } } Bài tập 2. 1. Hãy viết một chương trình tương đương, sử dụng cả hai kiểu khởi tạo trên. 2. Hãy chọn một cách khởi tạo tùy ý, để viết chương trình tính giá trị của biểu thức delta = b*b-4*a*c, với a, b, c lần lượt nhận các giá trị 1, 5, 3. 1.2.6. Khởi tạo giá trị cho biến tĩnh static Một biến được khai báo bằng từ khóa static thì nó chỉ khởi tạo giá trị đúng một lần khi biến được tạo ra. Thông thường những biến này được đặt vào trong một tệp tiêu đề .h để sử dụng cho toàn bộ chương trình. Ví dụ sau đây minh họa cho giá trị của biến static sẽ không khởi tạo lần thứ hai trong vòng lặp. C++ T r a n g | 29
  29. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ Chương trình Kết quả #include 3 4 using namespace std; 5 6 int main() 7 { for (int i=0; i #include using namespace std; #include int main() using namespace std; { int main() char a[] = “abc”; { char* b = “abc”; string a = “abc”; return 0; return 0; } } C++ T r a n g | 30
  30. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ Chúng ta cần lưu ý rằng, dù là một biến thuộc kiểu dữ liệu tham chiếu string, thì ta vẫn có thể sử dụng hai kiểu khởi tạo như trên. Điều này chỉ có thể áp dụng cho kiểu string mà thôi. Các kiểu dữ liệu tham chiếu khác không thể sử dụng hai cách khởi tạo này. Để biết thêm thông tin về kiểu string, các bạn nên tham khảo thêm thông tin về lớp string được cung cấp trong mục 4 của chương 17 trong cùng giáo trình này. 1.3. Hằng Định nghĩa: là một phần tử có giá trị cố định. Giá trị của nó được khởi tạo ngay khi hằng được tạo ra. Thông thường, người ta cũng sử dụng các chữ cái để đặt tên cho hằng. Tên hằng không chứa các kí tự đặt biệt, kí tự trắng hay bắt đầu bằng số, không được trùng với từ khóa. Trong C++, tên hằng thường được viết hoa toàn bộ. Hằng thường được chia ra làm: hằng số nguyên, hằng số thực, hằng kí tự, hằng xâu và hằng logic. 1.3.1. Hằng số nguyên Hằng số nguyên là các hằng có giá trị là số nguyên. Hằng số nguyên có thể được biểu diễn dưới dạng thập phân, bát phân, hoặc thập lục phân. Nếu hằng số nguyên dưới dạng thập phân thì có giá trị như số thập phân bình thường. Nếu là hằng số nguyên bát phân, thì nó bắt đầu bằng số 0 (ví dụ 011). Nếu là hằng số nguyên thập lục phân, thì nó bắt đầu bằng 0x (ví dụ 0x1b). Các quy tắc chuyển đổi số qua lại giữa các hệ đã được nghiên cứu trong học phần “Nhập môn tin học”. Nếu hằng số là số nguyên có dấu hoặc không dấu, có thể có một vài cách khai báo tương ứng. Hằng nguyên có dấu và không dấu 75 //int 75u //unsigned int 75l //long 75ul //unsigned long Các tiền tố và hậu tố trong hai cách sử dụng ở trên có thể viết thường hoặc viết hoa (0x12 hay 0X12 là như nhau; hoặc 75ul hay 75UL là như nhau). 1.3.2. Hằng số thực có dấu chấm động Chúng ta khảo sát số thực dưới dạng số thập phân dấu chấm động hoặc dưới dạng khoa học (hay còn gọi là dạng lũy thừa dạng E – tương ứng với lũy thừa 10). Ví dụ 314.159e-2 tương ứng với 3.14159 hay 6.02e23 tương ứng với 6.02*1023 C++ T r a n g | 31
  31. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ Một hằng số thực mặc định là double. Nếu muốn chỉ định kiểu dữ liệu cho nó, ta có thể sử dụng cú pháp tương tự như đối với hằng số nguyên (3.1415L tương ứng long double, 3.1415F tương ứng với float). Các kí tự e, f, l có thể biểu diễn dưới dạng chữ hoa hoặc chữ thường. 1.3.3. Hằng kí tự và hằng xâu kí tự Hằng kí tự được sử dụng trong dấu nháy đơn, còn hằng xâu kí tự được sử dụng trong dấu nháy kép. x Tên biến ‘x’ Kí tự x “x” Xâu kí tự x Trong hằng xâu kí tự, có thể chứa các kí tự đặc biệt như kí tự xuống dòng, đặt tab Sau đây là một vài kí tự đặc biệt đó và ý nghĩa của chúng. Kí hiệu Ý nghĩa \n Xuống dòng \r Di chuyển toàn bộ kí tự sau dấu \r đè lên các kí tự trước đó. Nếu số kí tự sau nhiều hơn số kí tự trước dấu \r, thì kết quả in ra sẽ là toàn bộ kí tự nằm sau. Ví dụ “abc\r1234” -> sẽ in ra 1234, nếu “abc\r12” -> sẽ in ra 12c. \t Đặt tab \v Đặt tab dọc \b Đặt backspace \f Đặt dấu form feed \a Tạo âm thanh beep \’, \”, \?, \\ Tạo các kí tự ‘, “, ?, \ Một hằng xâu kí tự có thể chứa nội dung trên nhiều dòng. Khi đó, để viết nội dung ở dòng mới, thì cuối dòng trước đó, ta bổ sung thêm kí tự \. Các xâu kí tự có thể được ghép với nhau nhờ vào kí tự trắng. Ví dụ “Hom nay toi di hoc\ Xâu kí tự viết trên nhiều dòng Ngay mai toi o nha” “Toi “ “yeu “ “lap trinh” Xâu kí tự ghép Khi sử dụng hằng xâu kí tự với kiểu dữ liệu là wchar_t, ta cần thêm tiền tố L bên trước xâu kí tự đó. Ví dụ L”Xau ki tu wchar_t”. Các quy tắc ở trên có thể áp dụng cho bất kì hằng xâu kí tự thuộc kiểu dữ liệu nào (char*, wchar_t*, string hoặc mảng kí tự tương ứng). C++ T r a n g | 32
  32. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ 1.3.4. Hằng logic Hằng logic có hai giá trị là true (đúng) và false (sai). Một biểu thức logic sẽ có kiểu dữ liệu là bool. Nó chỉ có thể nhận một trong hai giá trị true và false. Trong C, ta chỉ có thể sử dụng kiểu số nguyên (short, int, long ) để quy định giá trị của biểu thức logic: nếu giá trị nhận được là 0 – tương ứng với giá trị sai; ngược lại, nếu giá trị nhận được là khác 0 – tương ứng với giá trị đúng. Cách quy định này vẫn còn hoạt động tốt trên C++. Tuy nhiên, trong C++, người ta đã định nghĩa hai hằng số true và false và kiểu dữ liệu bool. Hằng số true tương ứng với giá trị 1 và hằng số false tương ứng với giá trị 0. Ta hoàn toàn có thể sử dụng giá trị true (hoặc 1) và false (hoặc 0). 1.3.5. Định nghĩa một hằng #define Khi định nghĩa một tên gọi cho hằng, ta có thể sử dụng nó thường xuyên mà không cần phải sắp xếp lại các biến chi phối bộ nhớ. Để định nghĩa một hằng, ta cần sử dụng từ khóa define. Cú pháp Ví dụ #define PI 3.14 #define tên_hằng giá_trị #define NewLine ‘\n’ Trong ví dụ trên, tôi đã định nghĩa hai hằng PI và Newline. Trong chương trình, tôi chỉ cần gọi nó để sử dụng, mà không cần triệu gọi đến giá trị cụ thể của nó. Chương trình tính diện tích hình tròn Kết quả #include 3.14 using namespace std; #define PI 3.14 int main() { double r = 1; double s; s = PI*r*r; cout<<s; return 0; } C++ T r a n g | 33
  33. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ Thực chất, #define không phải là một câu lệnh trong C++, nó chỉ là một định hướng tiền xử lý (directive for the preprocessor). Cho nên, ta cần lưu ý rằng, kết thúc phần khai báo này, không có dấu chấm phẩy (;). 1.3.6. Khai báo hằng const Để khai báo một hằng, ta sử dụng từ khóa const. Cấu trúc khai báo như sau: Cú pháp Ví dụ const int a = 10; const kiểu_dữ_liệu tên_hằng = giá_trị; const char x = ‘\t’; Trong ví dụ trên, ta có thể thấy cách khai báo hằng tương tự như khai báo biến, chỉ có duy nhất một điểm khác biệt là ta phải bổ sung từ khóa const vào trước khai báo này. Hằng và biến cũng tương tự nhau. Chúng chỉ khác nhau một điểm duy nhất là giá trị của hằng không thể thay đổi, còn biến thì có thể thay đổi. 1.4. Toán tử Chúng ta đã làm quen với biến và hằng, bây giờ chúng ta có thể tính toán giá trị của chúng. Các phép toán thực thi trên các biến hoặc hằng gọi là các toán tử. Và khi đó, các biến hoặc hằng đó gọi là các toán hạng. 1.4.1. Toán tử gán Toán tử gán dùng để gán giá trị cho một biến. Ví dụ a = 5; Câu lệnh gán sẽ thực hiện gán giá trị ở bên phải cho biến ở bên trái. Ta cũng có thể gán giá trị của hai biến cho nhau. Ví dụ a = b; Hãy quan sát và suy ngẫm đoạn chương trình sau: Chương trình [1.] #include [2.] using namespace std; [3.] int main() [4.] { [5.] int a, b; [6.] a = 10; [7.] b = 4; [8.] a = b; [9.] b = 7; [10.] cout<<”a=”<<a<<”, b=”<<b<<endl; [11.] return 0; [12.] } C++ T r a n g | 34
  34. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ Giải thích: Dòng lệnh [5.] khai báo hai biến nguyên a, b. Khi đó giá trị của chúng chưa được khởi tạo. Dòng lệnh [6.] khởi tạo giá trị cho biến a là 10, biến b chưa được khở tạo. Dòng lệnh [7.] khởi tạo giá trị cho biến b là 4, biến a vẫn không thay đổi (10). Dòng lệnh [8.] thực hiện việc gán giá trị của biến b cho biến a, khi đó b vẫn không thay đổi; a nhận giá trị của b, tức là 4. Dòng lệnh [9.] gán giá trị của biến b là 7, biến a không thay đổi. Do đó, giá trị cuối cùng của a là 4, b là 7. Output của chương trình sẽ là a=4, b=7. Chúng ta cần chú ý rằng, toán tử gán thực hiện theo nguyên tắc phải-sang- trái. Nghĩa là luôn lấy giá trị ở vế phải để gán cho vế trái. Khi đó, giá trị của biến ở vế trái thay đổi, còn ở vế phải không thay đổi. Toán tử gán có thể thực hiện trong các biểu thức phức tạp hơn. a = b + 2; Giá trị của a bằng giá trị của b cộng thêm 2 a = a + 1; Tăng giá trị của a lên 1 a = b = c = 5; Gán đồng thời nhiều giá trị. Nó tương ứng với tập các lệnh sau: c = 5; b = c; a = b; 1.4.2. Toán tử thực hiện phép toán số học Ngôn ngữ lập trình C++ hỗ trợ các toán tử số học sau: Toán tử Ý nghĩa + Phép cộng - Phép trừ * Phép nhân / Phép chia (chia lấy phần nguyên đối với hai số nguyên) % Chia lấy dư (chỉ với hai số nguyên) Chú ý rằng, phép chia có thể thực hiện trên số nguyên hoặc số thực. Nếu thực hiện phép chia trên hai số nguyên thì đây chính là kết quả của phép chia lấy phần nguyên. Còn nếu nó thực hiện trên hai số thực (hoặc một số thực và một số nguyên), thì kết quả được thực hiện trên phép chia bình thường. Như vậy, theo mặc định, hai số nguyên (hoặc thực) thực hiện phép toán tương ứng thì nó sẽ trả về kết quả nguyên (hoặc thực). Nếu phép toán thực hiện trên một số nguyên và một số thực, nó sẽ tự động chuyển đổi về kiểu cao hơn (thành số thực). Vậy làm thế nào để thực hiện phép chia 3 cho 2, nếu ta muốn nhận được kết quả là 1.5. Ta biết rằng 3 và 2 là hai số nguyên, nếu ta thực hiện phép chia 3/2 thì ta thu được số nguyên – là kết quả của phép chia lấy phần nguyên 3/2, tức là 1. Muốn thu được kết quả 1.5, ta cần chuyển đổi 3 và 2 về dạng số thực bằng một trong các cách sau: C++ T r a n g | 35
  35. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ . Khai báo 3 và 2 là các số thực (bằng cách quy định kiểu dữ liệu như float a = 3, float b = 2 hoặc 3.0, 2.0). . Chuyển đổi kiểu dữ liệu (Xem thêm phần toán tử chuyển đổi kiểu dữ liệu). 1.4.3. Toán tử gán hợp nhất Khi muốn thay đổi giá trị của một biến, chúng ta có thể sử dụng cách viết thông thường, nhưng trong C++ nó hỗ trợ các toán tử viết tắt. Toán tử Ví dụ Ý nghĩa Phạm vi += a+=b a=a+b Phép toán số học -= a-=b a=a-b Phép toán số học *= a*=b a=a*b Phép toán số học /= a/=b a=a/b Phép toán số học %= a%=b a=a%b Phép toán số học &= a&=b a=a&b Phép toán bit |= a|=b a=a|b Phép toán bit ^= a^=b a=a^b Phép toán bit >>= a>>=b a=a>>b Phép toán bit <<= a<<=b a=a<<b Phép toán bit 1.4.4. Toán tử tăng và giảm Một cách viết thu gọn hơn nữa, đó là sử dụng toán tử tăng và giảm. Nếu trong biểu thức a+=b, với b = 1 thì ta có thể viết thành a++. Tương tự, nếu a-=b, b = 1 thì ta có thể viết a . Chúng ta cũng lưu ý rằng, toán tử này có chút khác biệt. Nó có thể nằm trước hoặc nằm sau toán hạng. Có nghĩa là có thể có a++ hoặc ++a (tương ứng a hoặc a). Phép toán Ý nghĩa a++; Thực hiện phép toán trước, sau đó mới thực hiện toán tử. ++a; Thực hiện toán tử trước, sau đó mới thực hiện phép toán. a ; Tương tự a++; a; Tương tự ++a; Ví dụ Cách thực thi int a = 1; a = 1, b chưa khởi tạo int b = 1; a = 1, b = 1 a+=b++; Thực hiện phép toán a+=b trước, sau đó mới thực hiện phép toán b++. C++ T r a n g | 36
  36. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ Tức là a=2, b=2. Thực hiện phép toán ++b trước, sau đó mới thực hiện phép toán a+=b. a+=++b; Tức là b=2, a=3. 1.4.5. Toán tử so sánh Để thực hiện việc so sánh giá trị của hai biến hoặc hai biểu thức; ta có thể sử dụng toán tử so sánh. Giá trị của phép toán so sánh trả về kiểu bool. Toán tử Tên gọi Giá trị biểu thức “a Toán tử b” Đúng Sai == Bằng Nếu a bằng b Nếu a khác b != Khác Nếu a khác b Nếu a bằng b > Lớn hơn Nếu a lớn hơn b Nếu a nhỏ hơn hoặc bằng b = Lớn hơn hoặc bằng Nếu a lớn hơn hoặc Nếu a nhỏ hơn b bằng b Kết quả 1: 1 using namespace std; Kết quả 2: 0 int main() Kết quả 3: 1 { int a = 1; int b =2; cout =b); cout<<”Kết quả 3:”<< (a<=b); } Ta cần chú ý trong ví dụ này, cũng giống C, C++ chấp nhận giá trị 0 và 1 để quy định cho kiểu logic. Theo quy ước: true tương ứng với giá trị 1 (hoặc C++ T r a n g | 37
  37. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ khác 0), false tương ứng với giá trị 0. Mặc dù C++ hỗ trợ kiểu dữ liệu bool, nhưng nó vẫn dùng số nguyên 0 và 1 để biểu diễn tính đúng sai. Ta có thể tạm hiểu true và false là hai hằng số đã được định nghĩa sẵn, tương ứng với 1 và 0 (nhờ vào #define). Nếu mong muốn in ra giá trị là true/false, ta cần sử dụng định dạng dữ liệu cho đối tượng cout. Chi tiết, hãy tham khảo mục 8, chương 17 trong giáo trình này. Chú ý: . Hãy sử dụng kiểu dữ liệu bool thay vì dùng kiểu dữ liệu int để biểu diễn tính đúng sai. Khi sử dụng kiểu bool, hãy nhớ rằng giá trị đúng tương ứng với true; giá trị sai tương ứng với false (chú ý chúng được viết thường). . Hãy cẩn thận khi sử dụng toán tử so sánh bằng. Hãy chú ý rằng toán tử so sánh bằng là ==, khác với toán tử gán =. 1.4.6. Toán tử logic Phép toán a b Kết quả Toán tử phủ Phép toán true - false định ! một ngôi !a false - true Phép toán hai true true true Toán tử hội ngôi true false false && a&&b false true false false false false Phép toán hai true true true Toán tử tuyển ngôi true false true || a||b false true true false false false Ví dụ Kết quả #include Kết quả 1: 1 using namespace std; Kết quả 2: 0 int main() Kết quả 3: 0 { int a = true; int b =false; cout<<”Kết quả 1:”<<(a&&a); cout<<”Kết quả 2:”<< (!a&&b); cout<<”Kết quả 3:”<< !(a||b); C++ T r a n g | 38
  38. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ } Giải thích: Kết quả 1 – tương ứng với biểu thức a&&a=a, nghĩa là true – 1. Kết quả 2 – tương ứng với !a&&b. !a=false, false&&false=false – 0. Kết quả 3 – tương ứng với !(a||b), a||b=true||false=true, !(a||b)=!true=false – 0. Bài tập 3. Hãy lập trình kiểm tra tính đúng đắn của định luật De Morgan: a. !(a||b)=!a&&!b b. !(a&&b)=!a||!b 1.4.7. Toán tử điều kiện Toán tử điều kiện có dạng cú pháp như sau: (bt_điều_kiện)?(kết_quả_1):(kết_quả_2); Giải thích: trả về giá trị kết_quả_1 nếu bt_điều_kiện là đúng, ngược lại, nếu bt_điều_kiện là sai, thì trả về giá trị kết_quả_2. Chương trình Kết quả #include Max là: 2 using namespace std; int main() { int a = 1; int b = 2; int max = (a>b)?a:b; cout b, vì a=1, b=2, nên giá trị của nó là false. Chính vì vậy, biểu thức điều kiện sẽ nhận kết quả tương ứng với kết quả 2, tức là b. Toán tử điều kiện luôn trả về một giá trị cụ thể. Như trong ví dụ trên, ta thấy nếu biểu thức a>b đúng, thì giá trị max nhận được là số a; ngược lại là số b. Tuy nhiên, không nhất thiết cần phải có một giá trị xác định cho toán tử điều kiện. Ví dụ sau đây sẽ minh họa điều này Chương trình Kết quả #include 2 lon hon using namespace std; C++ T r a n g | 39
  39. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ int main() { int a = 1; int b = 2; (a>b)?(cout b đúng, thì in ra câu a lớn hơn, ngược lại sẽ in ra câu b lớn hơn. Ta cần lưu ý rằng, khi các câu lệnh nằm trong cặp dấu ngoặc của toán tử điều kiện, thì kết thúc câu lệnh không bao giờ có dấu chấm phẩy (;). Nếu muốn sử dụng một tập các câu lệnh trong cặp dấu ngoặc này, ta có thể sử dụng toán tử phân tách được đề cập trong mục tiếp theo. Ví dụ sau đây sẽ cho thấy việc sử dụng tập các câu lệnh bên trong cặp dấu ngoặc của toán tử điều kiện. Chương trình Kết quả #include |a-b|=1 using namespace std; int main() { int a = 1; int b = 2; int c; (a>b)?(c = a-b,cout b, thì giá trị tuyệt đối |a-b| = a-b; ngược lại nếu a<b, thì giá trị tuyệt đối |a-b| = b-a. Trong cặp dấu ngoặc đơn của toán tử điều kiện, câu lệnh gán c=a-b (hoặc c=b-a) và cout được phân tách bằng dấu phẩy (,). Một điều cần lưu ý, chúng ta không được phép khai báo biến trong cặp dấu ngoặc đơn này. Việc khai báo biến trong dấu ngoặc đơn, chỉ áp dụng duy nhất cho câu lệnh lặp for. 1.4.8. Toán tử phân tách Toán tử này kí hiệu là dấu phẩy. Nó dùng để phân tách hai hay nhiều biểu thức chứa trong một biểu thức phức hợp tương ứng. Ví dụ Kết quả 3 int a; int b; int c; C++ T r a n g | 40
  40. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ c = (a=1, b=2, a+b); cout > Ví dụ. Đối với số dương 0 1 0 1 1 (tương ứng 11 trong hệ thập phân) C++ T r a n g | 41
  41. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ >> 1 dịch sang phải 1 bit 0 0 1 0 1 (tương ứng với 5 trong hệ thập phân) Nghĩa là 11>>1=5. Đối với số âm 1 1 1 0 1 1 (tương ứng -11 trong hệ thập phân) >> 2 dịch sang phải 2 bit 1 1 1 1 1 0 (tương ứng -3 trong hệ thập p ân) Nghĩa là -11>>2=-3. Các bạn cũng cần lưu ý rằng, trong các biểu diễn ở trên, nếu hệ thống được chọn là 32 bit, thì chúng ta cần lấp đầy số bit này: - Nếu số dương thì các bit còn lại sẽ được bổ sung 0 vào phía trước. - Nếu số âm thì các bit còn lại sẽ được bổ sung 1 vào phía trước. Trong các ví dụ trên, phần dãy bit để trắng tương ứng với bit dấu – 1 tương ứng với – và 0 tương ứng với +. Toán tử dịch bit sang trái. Dịch chuyển toàn bộ dãy bit sang trái theo số bit được chỉ định. Ví dụ. Đối với số dương >= > nếu thực hiện trực tiếp trên số nguyên hệ thập phân, sẽ được tính như sau: a >b=a/2b. C++ T r a n g | 42
  42. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ 1.4.10. Toán tử chuyển đổi kiểu dữ liệu Toán tử này dùng để chuyển đổi một biến hay hằng thuộc kiểu dữ liệu này sang kiểu dữ liệu khác. Giả sử ta có biến int a = 3, int b = 2. Khi thực hiện phép chia để nhận được kết quả thực, ta chỉ cần viết như sau: (float)3/2. Hãy lưu ý rằng số 3 ở đây đã bị chuyển thành kiểu thực, và việc thực hiện phép chia một số thực cho số nguyên sẽ trả về kiểu thực 1.5. Nếu ta viết 3/(float)2, kết quả cũng tương tự. Ngược lại, nếu viết (float)(3/2) thì kết quả lại khác. Sở dĩ như vậy là vì, nó sẽ thực hiện phép chia nguyên 3/2 (kết quả là 1), sau đó nó sẽ chuyển giá trị 1 nguyên này sang 1 thực. Do đó, giá trị thu được vẫn là 1, nhưng thuộc kiểu số thực (tức là 1.0f). Cách biểu diễn sự chuyển đổi một biến thuộc kiểu dữ liệu này, sang kiểu khác chỉ có thể thực hiện nếu kiểu của chúng tương đương. Ta có thể chuyển số thành số (sau này khi học về hướng đối tượng, ta có thể chuyển giữa các đối tượng trong cùng một cây phả hệ). Ta không thể chuyển đổi từ số thành xâu, hay ngược lại (bằng cách thực hiện phép toán chuyển đổi kiểu). Ta có thể chuyển đổi một xâu số thành số và một số thành xâu số bằng nhiều cách khác nhau, nhưng việc sử dụng toán tử chuyển đổi kiểu là không được phép. Khi chuyển đổi, ta sử dụng một trong các cú pháp sau: (kiểu_dữ_liệu)biến hoặc (kiểu_dữ_liệu)(biến) hoặc kiểu_dữ_liệu(biến). Chúng ta nên sử dụng kiểu thứ 2 hoặc 3 để tránh các nhầm lẫn đáng tiếc khi biểu thức phức tạp. 1.4.11. Các toán tử khác Trong phần lập trình hướng đối tượng, chúng ta sẽ làm quen thêm nhiều toán tử khác. Theo trình tự trình bày trong cuốn giáo trình này, chúng ta sẽ chưa thảo luận thêm về chúng. Ta sẽ tìm hiểu chi tiết trong phần hướng đối tượng của cuốn giáo trình này. 1.4.12. Thứ tự ưu tiên của các toán tử Trong toán học, chúng ta biết rằng khi tính giá trị của một biểu thức, thì luôn có sự ưu tiên của các toán tử như: phép nhân thực hiện trước phép cộng, phép chia và nhân thực hiện đồng thời, ưu tiên từ trái sang phải Trong các ngôn ngữ lập trình nói chung cũng như C++ nói riêng, các toán tử cũng có những độ ưu tiên nhất định. Trong một biểu thức phức tạp, ta cần chú ý đến độ ưu tiên của các toán tử, điều này rất dễ gây ra sai sót. Trong bảng sau đây, tôi xin đưa ra thứ tự ưu tiên của các toán tử trong lập trình C++. Mức ưu tiên Toán tử Độ ưu tiên cùng loại 1 :: Trái-sang-phải 2 () [] . -> ++ (hậu tố) dynamic_cast Trái-sang-phải static_cast reinterpret_cast const_cast typeid 3 ++ (tiền tố) ~ ! sizeof new delete Phải-sang-trái * & + - (dấu dương âm) C++ T r a n g | 43
  43. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ 4 (type) (chuyển đổi kiểu) Phải-sang-trái 5 .* ->* Trái-sang-phải 6 * / % Trái-sang-phải 7 + - (phép toán công, trừ) Trái-sang-phải 8 > Trái-sang-phải 9 = Trái-sang-phải 10 == != Trái-sang-phải 11 & Trái-sang-phải 12 ^ Trái-sang-phải 13 | Trái-sang-phải 14 && Trái-sang-phải 15 || Trái-sang-phải 16 ?: Phải-sang-trái 17 = *= /= %= += -= >>= <<= &= ^= |= Phải-sang-trái 18 , Trái-sang-phải Các toán tử được thực hiện theo mức ưu tiên từ trên xuống. Nếu các toán tử cùng mức, nó sẽ được thực hiện theo độ ưu tiên cùng loại. Ví dụ: . a = (b=0, c=0, b+c). Toán tử gán = có độ ưu tiên 17, các toán tử cộng + có độ ưu tiên 7, toán tử () có độ ưu tiên 2 và toán tử , có độ ưu tiên 18. Do đó, toán tử () sẽ được thực hiện trước. Bây giờ ta xét các toán tử trong dấu (), chú ý rằng các biểu thức b=0, c=0, b+c là các biểu thức riêng biệt, chúng được phân tách bởi toán tử phân tách (,). Theo thứ tự ưu tiên của toán tử phẩy, nó sẽ thực hiện từ trái-sang-phải. Nghĩa là b=0, c=0 sau đó là b+c. Cuối cùng nó sẽ thực hiện toán tử gán giá trị của biểu thức phức hợp bên phải cho bên trái. Kết quả là 0. . a = (1+2)*3/2++. Toán tử gán (độ ưu tiên 17), toán tử + (độ ưu tiên 7), toán tử * (độ ưu tiên 6), toán tử / (độ ưu tiên 6), toán tử ++ hậu tố (độ ưu tiên 2) và toán tử () (độ ưu tiên 2). Toán tử hậu tố ++ và toán tử () sẽ thực hiện trước. Theo độ ưu tiên cùng loại, nó sẽ thực thi từ trái-sang-phải. Như vậy, toán tử () sẽ được thực hiện đầu tiên. Nghĩa là ta nhận được biểu thức a = 3*3/2++. Tiếp theo, nó thực hiện toán tử hậu tố ++, tuy nhiên toán tử này chỉ tăng giá trị của 2 lên 1 sau khi thực hiện xong các phép toán trong biểu thức. Đến thời điểm này, ta nhận được biểu thức a=3*3/2. Toán tử * và / có cùng độ ưu tiên, nó sẽ được thực hiện theo thứ trự từ trái sang phải, nghĩa là a=9/2=4. Kết quả 4. Lưu ý. Trong ví dụ thứ hai, việc sử dụng phép toán 2++ là không hợp lệ. Ở đây, chỉ có tác dụng minh họa trực quan. Còn 2 là một hằng số, ta không thể thực hiện phép toán 2++ để làm thay đổi giá trị của hằng. Trong C++, chúng ta cần thực hiện phép gán b = 2; sau đó là b++. Nghĩa là ta cần biểu diễn biểu thức như sau để thu được một kết quả chính xác có thể bảo đảm thực thi được trong C++. C++ T r a n g | 44
  44. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ a = (b=2, (1+2)*3/b++) Bài tập 4. Tính toán các biểu thức sau, dựa vào độ ưu tiên của toán tử, sau đó, viết chương trình trên C++ để kiểm tra kết quả. a. 2+2*4%3+ ++2; b. 2++ + ++2*(3 - 2) c. 5++ - 3== 2-(4+2%3) d. 5>>2^3*(1+2) 1.5. Xuất – nhập cơ bản Đến thời điểm này, chúng ta đã biết hai cách thức để xuất dữ liệu ra màn hình nhờ vào việc sử dụng đối tượng cout và hàm printf. Trong chương này, chúng ta sẽ tìm hiểu cụ thể hơn về cách xuất-nhập dữ liệu nhờ vào thiết bị nhập dữ liệu là bàn phím, và thiết bị hiển thị dữ liệu xuất ra là màn hình máy tính. Trong thư viện chuẩn của C++, các hàm xuất nhập cơ bản nằm trong tệp header là iostream. Đối với thư viện này ta cần lưu ý một số điểm. Có hai lớp thư viện có chức năng hỗ trợ các hàm xuất nhập cơ bản đó là iostream và iostream.h. Về bản chất, cách thức sử dụng chúng không có nhiều sự khác biệt. Tuy nhiên việc sử dụng thư viện iostream có nhiều ưu điểm hơn hẳn so với thư viện iostream.h. Thư viện iostream.h đã ra đời cách đây quá lâu (trên 15 năm) trong khi thư viện iostream mới hơn rất nhiều. Việc sử dụng một thư viện mới, chuẩn mực hơn bao giờ cũng tốt hơn. Thư viện iostream hỗ trợ cả kiểu char lẫn kiểu w_char. Thư viện iostream được đặc tả trong namespace std, trong khi thư viện iostream.h được khai báo toàn cục. Việc khai báo toàn cục bao giờ cũng chiếm dụng không gian bộ nhớ lớn hơn. Vì vậy, nếu muốn thực hiện việc nhập xuất dữ liệu cơ bản trong C++, ta nên sử dụng thư viện iostream thay vì sử dụng iostream.h. Tổng quát hóa, nếu các lớp thư viện có hai dạng tồn tại song song là .h và không có .h (string và string.h, new và new.h ), thì chúng ta nên sử dụng thư viện không có .h. Trong trường hợp không có dạng tương ứng, ta bắt buộc phải sử dụng thư viện .h (ví dụ math.h). 1.5.1. Xuất dữ liệu chuẩn cout Đầu tiên, ta khảo sát việc xuất dữ liệu ra màn hình nhờ đối tượng cout. Nó được sử dụng kết hợp với toán tử chèn dữ liệu << (kí hiệu giống toán tử dịch bit trái). Cú pháp: cout<<biến_1<< <<biến_n; Trong đó, biến_1, ,biến_n: là các biến số. Chúng đã được khởi tạo giá trị. Nếu biến chưa khởi tạo giá trị, ta sẽ nhận được một lỗi khi thực thi chương C++ T r a n g | 45
  45. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ trình. Chương trình dịch sẽ thông báo về việc sử dụng biến mà không khởi tạo giá trị cho nó. Các biến này có thể là biến thuộc kiểu dữ liệu nguyên thủy hoặc tham chiếu. Đối với các biến là các đối tượng thể hiện của các lớp, ta sẽ thảo luận ở mục “chồng chất toán tử nhập xuất” trong chương lập trình hướng đối tượng. Sau đây là một vài ví dụ về việc sử dụng đối tượng cout: cout > (giống toán tử dịch bit phải). Sau toán tử này, bắt buộc là một biến để lưu dữ liệu được tách ra. Cú pháp: cin>>biến_1>> >>biến_n; Các biến số biến_1, , biến_n cần được khai báo. Thông thường, những biến này chưa được khởi tạo giá trị. Sau đây là một vài ví dụ về việc sử dụng đối tượng cout: int age; cin>>age; float f; cin>>f; string s; cin>>s; Chú ý rằng kiểu dữ liệu của biến được sử dụng trong đối tượng cin này. Nếu có một sự vi phạm nào về kiểu dữ liệu (ví dụ biến là int, nhưng khi nhập ta lại nhập vào một kí tự không phải là số) thì chương trình dịch sẽ bỏ qua việc khởi tạo giá trị cho biến đó. Chương trình hoàn toàn không phát sinh lỗi (process returned 0). C++ T r a n g | 46
  46. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ Khi sử dụng đối tượng cout và cin, ta cần khai báo không gian sử dụng namespace là std. Hoặc, có thể viết ngắn gọn hơn std:: Chương trình 1 Chương trình 2 #include #include using namespace std; int main(){ int main(){ std::cout #include using namespace std; int main() { string s; cout<<”Nhap ten: “; getline(cin, s); cout<<”Chao ban “<<s; return 0; } C++ T r a n g | 47
  47. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ 1.5.3. Nhập dữ liệu nhờ lớp stringstream Để sử dụng lớp stringstream, chúng ta cần khai báo tệp header là . Lớp này cho phép một đối tượng dựa trên cơ sở của xâu có thể được chuyển đổi như một luồng stream. Trong thư viện sstream, ta có ba lớp đối tượng luồng xâu cơ bản: stringstream, istringstream và ostringstream. Các luồng này có thể được sử dụng để tách hoặc chèn xâu, nó đặc biệt hữu dụng khi chuyển một xâu thành số và ngược lại. Ví dụ, nếu muốn tách một số integer từ một xâu “1201”, ta có thể viết như sau: string mystr = “1201”; int mynum; stringstream(mystr)>>mynum; Đây không phải là cách thức duy nhất giúp ta chuyển đổi một xâu thành số. Trong thư viện string, cung cấp cho chúng ta các hàm để thực thi công việc đó như hàm atof (xâu số thực thành số thực), atoll (xâu thành số nguyên dài thành số nguyên dài), Tuy nhiên, các hàm này chủ yếu làm việc với xâu kí tự của C, tức là char*. Ví dụ sau đây sẽ cho thấy cách sử dụng lớp stringstream để nhập dữ liệu từ bàn phím. Chương trình #include #include using namespace std; int main() { string mystr; int mynum; getline(cin,mystr); stringstream(mystr)>>mynum; cout<<mynum; return 0; } Thay vì trực tiếp trích lọc số nguyên nhập vào, ta sử dụng hàm getline để trích lọc dữ liệu nhập vào dưới dạng xâu kí tự. Sau đó, chúng ta sử dụng lớp stringstream để chuyển đổi xâu đó thành số. Ví dụ sau đây cho phép chúng ta C++ T r a n g | 48
  48. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ nhập vào một dãy giá trị, sau đó in ra tổng của các số vừa nhập. Số nhập vào được phân tách với nhau bằng dấu hai chấm :. Chương trình Kết quả #include 1:2:3:4:5 #include Sum = 15 using namespace std; int main() { string temp, s; getline(cin, s); istringstream ss(s); double sum = 0; while (getline(ss, temp, ':') ) { float a; stringstream(temp)>>a; sum+=a; } cout<<”Sum = “<<sum; return 0; } Giải thích: trong ví dụ trên, hàm getline có thêm một biến thể (sau này, chúng ta sẽ gọi chúng là các chồng chất hàm). Nó có tất cả 4 dạng biến thể. Phương pháp này, thường được sử dụng khi dữ liệu nhập vào quá nhiều. Chúng ta có thể cho người dùng nhập vào thành một xâu, khi đó ta sẽ tiến hành xử lý nhờ lớp istringstream này. Để có cái nhìn cụ thể hơn về các lớp stringstream, istringstream, ostringstream; hãy tham khảo thêm thông tin trong phần trợ giúp của MSDN5 (Microsoft Developer Network). 1.6. Các cấu trúc lệnh điều khiển Một chương trình khi thực thi, nó không chỉ đơn thuần là một dãy các câu lệnh tuần tự. Trong quá trình xử lý, nó có thể kiểm tra điều kiện rồi thực thi đoạn mã, lặp đi lặp lại một đoạn mã nào đó Với mục đích đó, C++ cung cấp cho chúng ta các cấu trúc điều khiển. 5 C++ T r a n g | 49
  49. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ Với các cấu trúc điều khiển mà chúng ta sẽ tìm hiểu trong chương này, ta sẽ đề cập đến hai khái niệm: câu lệnh (mệnh đề - statement) và khối lệnh (block). Câu lệnh: là một lệnh được kết thúc bằng dấu chấm phẩy (;). Nó có thể là lệnh đơn giản hoặc lệnh có cấu trúc. Các lệnh đơn giản như nhập xuất dữ liệu; các khai báo biến, hằng; lệnh gán Các lệnh gán, xuất nhập là các lệnh đơn. Các lệnh điều kiện, lựa chọn, lặp mà chúng ta tìm hiểu trong chương này là các lệnh có cấu trúc. Khối lệnh: là một dãy các câu lệnh. Trong C++, khối lệnh được đặt trong cặp dấu {}. 1.6.1. Cấu trúc lệnh có điều kiện: if và else Từ khóa if thường được sử dụng khi muốn thực thi một đoạn chương trình với một điều kiện nào đó. Cấu trúc của câu lệnh if trong trường hợp này Cú pháp: if (biểu_thức_điều_kiện_đúng) { Các_lệnh; } Giải thích: kiểm tra giá trị của biểu thức điều kiện, nếu đúng thì các lệnh bên trong sẽ được thực hiện; ngược lại, lệnh sẽ không được thực hiện. Ví dụ if (x>0) cout 0)&&(y>0)) { cout<<x<<” la so duong”<<endl; cout<<y<<” la so duong”; } Nếu có nhiều câu lệnh chịu sự chi phối của câu lệnh if, thì chúng sẽ được đặt trong khối lệnh. Trong trường hợp nếu biểu_thức_điều_kiện sai, ta cần thực thi một mệnh đề khác. Khi đó, ta sẽ sử dụng thêm từ khóa else. C++ T r a n g | 50
  50. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ Cú pháp: if(biểu_thức_điều_kiện_đúng) { }else { } Ví dụ if (x%2==0) cout 0) cout<<x<<” la so duong”; else if(x<0) cout<<x<<” la so am”; else cout<<x<<” la so 0”; Bài tập 5. 1. Viết chương trình giải phương trình bậc 2, với các hệ số nhập vào từ bàn phím. 2. Viết chương trình tính giá trị của hàm số sau: ( ) (| ( )|) ( ) { ( ) 3. Viết chương trình giải hệ phương trình ba ẩn đầy đủ bằng công thức định thức Cramer. C++ T r a n g | 51
  51. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ 1.6.2. Cấu trúc lặp Trong thực tế, chúng ta gặp nhiều tình huống lặp đi lặp lại. Với mục đích này, ngôn ngữ C++ cung cấp cho chúng ta các cấu trúc lặp tương ứng. 1. Vòng lặp while Cú pháp: while (biểu_thức_điều_kiện_đúng) { . } Giải thích: Nếu biểu_thức_điều_kiện đúng, các lệnh bên trong vòng lặp sẽ được thực hiện cho đến khi nó nhận giá trị sai. Ví dụ Kết quả #include Nhap n:5 using namespace std; 5 int main() 4 { 3 int n; 2 cout >n; while (n>0){ cout 0. Điều kiện này đúng, nên các lệnh trong vòng lặp sẽ được thực hiện. Nó sẽ in ra giá trị của n là 5. Sau đó, giá trị của n giảm đi 1, tức là n = 4. Vòng lặp lại tiếp tục thực hiện, vì n>0 còn đúng. Quá trình này cứ tiếp tục, cho đến khi n=0. Khi đó, điều kiện n>0 là sai. Do đó, vòng lặp sẽ dừng lại. Giá trị in ra màn hình là các số từ 5 giảm đến 1. Lưu ý: khi sử dụng vòng lặp while cần lưu ý các điểm sau đây: C++ T r a n g | 52
  52. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ . Vòng lặp phải có tính dừng. Nghĩa là biểu_thức_điều_kiện phải có trường hợp sai. Trong một số tình huống, người ta vẫn sử dụng vòng lặp vô hạn, nhưng cần có cơ chế để thoát khỏi vòng lặp khi cần thiết. . Nếu có nhiều lệnh chịu sự chi phối của while, thì chúng cần được đặt trong dấu khối lệnh. 2. Vòng lặp do while Cú pháp: do { . }while (biểu_thức_điều_kiện_đúng); Giải thích: Thực hiện các lệnh trong vòng lặp, sau đó kiểm tra biểu_thức_điều_kiện. Nếu biểu_thức_điều_kiện còn đúng, thì tiếp tục lặp. Ví dụ Kết quả #include Nhap n:5 using namespace std; 5 int main(){ 4 int n; 3 cout >n; 1 do { cout 0); return 0; } Giải thích: đầu tiên nhập vào giá trị cho biến n. Giá trị n nhập vào ở đây là 5. Vòng lặp do while sẽ thực thi các lệnh bên trong nó. Nó sẽ in ra giá trị của n là 5. Sau đó, giá trị của n giảm đi 1, tức là n = 4. Vòng lặp kiểm tra giá trị của biểu thức n>0. Vì biểu thức này đúng, nên nó tiếp tục lặp. Quá trình này cứ tiếp tục cho đến khi n=1. Giá trị n=1 vẫn được in ra. Sau đó, giá trị của n giảm xuống 1, tức là n=0. Sau khi kiểm tra n>0 không còn đúng nữa, vòng lặp kết thúc. Kết quả in ra tương tự như vòng lặp while ở trên, tuy nhiên thứ tự thực hiện là hoàn toàn khác so với while. C++ T r a n g | 53
  53. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ Lưu ý: khi sử dụng vòng lặp do while cần lưu ý các điểm sau đây: . Vòng lặp phải có tính dừng. Nghĩa là biểu_thức_điều_kiện phải có trường hợp sai. Trong một số tình huống, người ta vẫn sử dụng vòng lặp vô hạn, nhưng cần có cơ chế để thoát khỏi vòng lặp khi cần thiết. . Nếu có nhiều lệnh chịu sự chi phối của do while, thì chúng cần được đặt trong dấu khối lệnh. . Vòng lặp do while luôn thực hiện các lệnh bên trong nó, ít nhất 1 lần. 3. Vòng lặp for Cú pháp: for (biểu_thức_khởi_tạo; biểu_thức_giới_hạn; biểu_thức_tăng_giảm) { . } Giải thích: Thực hiện vòng lặp, với số vòng lặp từ biểu_thức_khởi_tạo cho đến biểu_thức_giới_hạn theo mức tăng là biểu_thức_tăng_giảm. Ví dụ Kết quả #include Nhap n:5 using namespace std; 0 int main() 1 { 2 int n, i; 3 cout >n; 5 for (i=0; i<=n;i++) { cout<<i<<endl; }; return 0; } Giải thích: đầu tiên nhập vào giá trị cho biến n. Giá trị n nhập vào ở đây là 5. Vòng lặp for sẽ thực thi các lệnh bên trong với số vòng lặp là từ 0 đến 5, theo bước nhảy là 1 – tương ứng với i++. Lưu ý: khi sử dụng vòng lặp for cần lưu ý các điểm sau đây: C++ T r a n g | 54
  54. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ . Các tham số trong vòng lặp for có thể khuyết một hoặc vài (thậm chí là tất cả) tham số. Tuy nhiên, dấu chấm phẩy là luôn bắt buộc. Số bước lặp của vòng lặp for sẽ được tính như sau: . Nếu có nhiều lệnh chịu sự chi phối của for, thì chúng cần được đặt trong dấu khối lệnh. . Ta có thể thực hiện việc khai báo biến trực tiếp bên trong dấu ngoặc đơn của vòng lặp for. Ví dụ Kết quả #include Nhap n:5 using namespace std; 0 int main() 1 { 2 int n; 3 cout >n; 5 for (int i=0; i 2 using namespace std; 1 int main() 0 { C++ T r a n g | 55
  55. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ int n = 2; for(;;){ cout 0 using namespace std; 2 int main() 4 { 6 for(int i=0;i<10;i++){ 8 if (i%2!=0) continue; cout<<i<<endl; } return 0; } Giải thích: Vòng lặp for sẽ thực thi các lệnh bên trong nó. Biến i chạy từ 0 đến 9, kiểm tra điều kiện i có phải là số chẵn hay không. Nếu i là số lẻ, thì câu lệnh continue sẽ được thực hiện và bước lặp hiện tại sẽ bị bỏ qua. Nếu i là số chẵn, thì lệnh continue sẽ không được gọi và bước lặp hiện tại vẫn được thực hiện. Do đó, lệnh cout chỉ được thực hiện trong trường hợp biến i là chẵn. Như vậy, mỗi khi giá trị i là chẵn, nó sẽ in kết quả. Nếu giá trị của i là lẻ, thì kết quả sẽ không được in ra. c. Lệnh goto Lệnh goto cho phép tạo ra một bước nhảy đến một nhãn được ấn định sẵn. Tên nhãn sẽ được đặt như sau tên_nhãn: và lệnh goto sẽ nhảy đến tên nhãn. Một lời khuyên cho chúng ta là nên hạn chế tối đa việc sử dụng lệnh goto. Bởi vì lệnh goto thường làm phá vỡ cấu trúc của lập trình hiện đại. Nhiều ngôn ngữ lập trình họ nhà C ra đời sau C++ như Java đã tuyệt giao hoàn toàn với câu lệnh goto này. C++ T r a n g | 56
  56. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ Chương trình Kết quả #include 5 using namespace std; 4 int main() 3 { 2 int n = 5; 1 loop://Tên nhãn cout 0) goto loop; return 0; } Giải thích: Giá trị khởi tạo của biến n là 5. Nhãn được đặt tên là loop. Nhãn có thể hiểu như một vị trí được đánh dấu (bookmark). Chương trình tiến hành in giá trị của biến n. Sau đó, giảm giá trị của n đi. Câu lệnh điều kiện, kiểm tra giá trị của biểu thức n>0, nếu đúng thi lệnh goto được gọi và nó sẽ được chuyển đến vị trí đã được đánh dấu là loop. Chương trình lại thực thi thêm lần nữa kể từ vị trí loop đó. Nếu n<=0, lệnh goto không được gọi. Chương trình kết thúc. Việc sử dụng các câu lệnh lặp, hoàn toàn có thể thay thế cho lệnh goto. Hãy luôn ghi nhớ: CHỈ NÊN sử dụng goto khi thực sự cần thiết. d. Lệnh exit Lệnh exit dùng để thoát khỏi chương trình và trả về một mã được chỉ định. Mã chỉ định này tùy thuộc vào hệ điều hành, nó có thể sử dụng trong chương trình theo quy ước như sau: nếu chương trình kết thúc bình thường, thì mã chương trình là 0; nếu có một sự cố không mong muốn xảy ra, thì mã chương trình là một giá trị khác 0. void exit(int mã_chỉ_định); Nếu tham số mã_chỉ_định không được cấp vào, tức là exit (không có dấu ngoặc đơn), thì nó sẽ tiến hành theo mặc định – tức giá trị 0. Hàm exit nằm trong thư viện stdlib.h. Đây là một hàm tương đối cũ nằm trong thư viện .h. Ta chỉ có thể sử dụng lệnh exit nếu khai báo thư viện stdlib.h mà không có thư viện tương ứng là stdlib. 1.6.3. Cấu trúc lựa chọn: switch Cú pháp: switch(biểu_thức){ case hằng_1: nhóm_các_lệnh; break; C++ T r a n g | 57
  57. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ case hằng_2: nhóm_các_lệnh; break; default: nhóm_các_lệnh; } Giải thích: kiểm tra giá trị của biểu thức, nếu giá trị của biểu thức rơi vào danh sách hằng, thì nó sẽ thực hiện các lệnh trong từng trường hợp case tương ứng (nếu là hằng_1 – các lệnh trong trường hợp case hằng_1, .). Nếu biểu thức không thuộc vào danh sách hằng, thì nó sẽ thực hiện lệnh trong trường hợp default. Chương trình Kết quả #include Ban la nam hay nu b/g:b using namespace std; int main() Nam { char n; cout >n; switch(n) { case ‘b’: cout<<”Nam”; break; case ‘g’: cout<<”Nu”; break; default: cout<<”Khong xac dinh”; } return 0; } Giải thích: chương trình buộc người dùng nhập vào một kí tự (b – boy) hay (g – girl). Nếu người dùng nhập vào một kí tự khác, chương trình vẫn tính đến trường hợp này. Kí tự mà người dùng nhập vào được lưu trong biến n. Câu lệnh switch sẽ kiểm tra biến nhập vào đó có nằm trong danh sách hằng hay không (danh sách hằng ở đây là ‘b’ và ‘g’). Nếu có, thì tương ứng với ‘b’ nó sẽ thực C++ T r a n g | 58
  58. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ hiện trường hợp case ’b’, nếu là ‘g’ nó sẽ thực hiện trường hợp case ‘g’. Lệnh break trong mỗi trường hợp có tác dụng là thoát ra khỏi câu lệnh lựa chọn (cũng mang tính lặp), nếu không có lệnh break, tất cả các trường hợp đều được xét duyệt và nếu rơi vào trường hợp nào thì các lệnh tương ứng sẽ được thực thi, đồng thời lệnh ở trường hợp bên dưới nó cũng được thực thi. Trong trường hợp, kí tự nhập vào không tương ứng trong danh sách hằng, nó sẽ thực thi trường hợp default. Vì default là trường hợp cuối cùng, nên nó không cần lệnh break. Chú ý: Lệnh switch chỉ được lựa chọn để sử dụng khi cần kiểm tra giá trị của một biểu thức có tương ứng với một tập các hằng số nào đó hay không (sự tương ứng ở đây có thể là thuộc hoặc không thuộc tương ứng với khái niệm trong tập hợp). Các hằng_1, hằng_2, có thể là một vùng liên tục, hoặc gián đoạn (như các số từ 0 1, ‘a’ ’d’, ). Nhưng nhất thiết các giá trị tương ứng với các trường hợp case phải là hằng số (hoặc khoảng hằng). int a = 1; Liên tục switch(a>0) { case true: cout<<"Duong"; break; case false: cout<<"Am"; break; default: cout<<"Khong"; } int a = 1; Rời rạc switch(a) { case 1: case 2: case 3: cout<<"Xuan"; break; case 4: case 5: case 6: cout<<"Ha"; break; case 7: case 8: case 9: cout<<"Thu"; break; case 10: case 11: case 12: cout<<"Dong"; break; default: C++ T r a n g | 59
  59. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ cout<<"Khong phai thang cua nam"; } Biểu thức trong lệnh switch nhất thiết không phải là một kiểu có cấu trúc (mảng, xâu, ). Ví dụ sau đây sẽ phát sinh lỗi khi biên dịch, do biểu thức tương ứng với một xâu. string s = “abc”; Error switch(s) { case “a”: case “ab”: case “abc”: default: } Trong hầu hết các ngôn ngữ lập trình, đại đa số đều không cho phép tham số trong switch là một xâu (cũng như lệnh case of trong họ Pascal – Delphi). Tuy nhiên, ngôn ngữ C# vẫn hỗ trợ xâu kí tự trong tham số của switch dù nó là một dẫn xuất của C++. Một điều cần lưu ý, về bản chất thì câu lệnh switch sẽ tương ứng với một dãy các câu lệnh if. Chúng hoàn toàn có thể thay thế cho nhau. Cũng tương tự, các câu lệnh lặp while, do while và for cũng có thể thay thế cho nhau một cách hoàn toàn. Có nghĩa là chúng ta chỉ cần nắm được cú pháp của một trong ba câu lệnh lặp này là có thể vận dụng trong mọi trường hợp. Tuy nhiên, chúng vẫn được sử dụng trong các trường hợp mang tính đặc trưng. Điều này rất hữu ích cho những người đã từng làm quen với ngôn ngữ lập trình họ Pascal – Delphi. Bảng sau đây tổng hợp các cách sử dụng của các lệnh có cấu trúc thường dùng. Tên lệnh Cách dùng if else Khi cần kiểm tra một hoặc một vài điều kiện mang tính chất logic. switch Khi cần kiểm tra điều kiện hoặc tính thuộc vào của một biến số/biểu thức trong một danh sách hằng tương ứng. for Lặp có số vòng lặp xác định while Cần kiểm tra điều kiện lặp trước khi thực hiện lệnh, lặp không xác định số vòng lặp. do while Kiểm tra điều kiện lặp sau khi thực hiện lệnh, lặp không xác định số vòng lặp. break Cần thoát khỏi vòng lặp. continue Bỏ qua vòng lặp hiện tại, thực thi bước lặp tiếp theo. goto Nhảy đến một nhãn được chỉ đinh. Nên tránh sử dụng, chỉ sử dụng trong những trường hợp thực sự cần thiết. C++ T r a n g | 60
  60. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ Bài tập 6. 1. Sử dụng các cấu trúc lặp for, while, do while, goto để xây dựng các chương trình tính tích phân sau (với mỗi cấu trúc xây dựng mỗi chương trình) bằng phương pháp hình chữ nhật (trái, phải hoặc trung tọa). ∫ ( ) 2. Lựa chọn cấu trúc lệnh phù hợp, để tính giá trị của chuỗi hữu hạn sau đây ∑ 3. Tính các tổng sau đây ( ) ( ) với n, x nhập vào từ bàn phím. Yêu cầu xây dựng hàm. 1.7. Hàm Hàm là một tập hợp các câu lệnh, nó được thực thi khi được gọi từ một vị trí khác trong chương trình. Hãy quan sát lược đồ bên dưới đây: Hình 1.1 – Sơ đồ minh họa việc sử dụng hàm C++ T r a n g | 61
  61. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ Trong lược đồ này, các hàm được viết trong các thư viện khác nhau. Trong chương trình chính, chúng ta có thể gọi các hàm trong các thư viện, lẫn các hàm được khai báo trong chương trình chính. Nhờ vào việc sử dụng hàm, chúng ta có thể phân chia chương trình thành các modul nhỏ. Đôi lúc, người ta gọi cách phân chia chương trình thành các hàm như thế này là cách giải quyết bài toán theo phương pháp “chia để trị”. Cách phân chia này có rất nhiều ưu điểm: . Làm cho chương trình trở nên gọn gàng dễ đọc hơn. . Dễ cải biên chương trình. . Dễ kiểm tra theo các modul. Đó là ý tưởng để xây dựng hàm. Vậy hàm được khai báo và sử dụng như thế nào. Chúng ta sẽ tìm hiểu trong chương này. 1.7.1. Khai báo và sử dụng hàm Cú pháp: kiểu_dữ_liệu tên_hàm(danh_sách_tham_số) { Thân hàm; } Trong đó, o kiểu_dữ_liệu: là kiểu dữ liệu mà hàm trả về. o tên_hàm: là tên của hàm, do người lập trình đặt. Tên hàm không được chứa kí tự đặc biệt, không được bắt đầu bằng số, không chứa kí tự trắng, không trùng với từ khóa. o danh_sách_tham_số: là danh sách các tham số dùng như các biến cục bộ. Nếu có nhiều tham số, thì chúng sẽ được phân tách theo các dấu phẩy. o Thân hàm: là nội dung mà người lập trình xây dựng nên. Nếu hàm trả về kiểu dữ liệu khác void, ta cần sử dụng lệnh return để trả về biến chứa kết quả và có cùng kiểu dữ liệu với kiểu dữ liệu của hàm. Nếu hàm trả về kiểu dữ liệu void thì điều này là không cần thiết. Ví dụ Kết quả #include 3 using namespace std; int add(int a, int b) { return a+b; C++ T r a n g | 62
  62. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ } int main() { cout 3 using namespace std; int add(int a, int b); int main() { cout<<add(1, 2); return 0; } int add(int a, int b) { return a+b; } C++ T r a n g | 63
  63. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ Trong khai báo hàm dạng này, cấu trúc khai báo hàm khuyết phần thân hàm. Chỉ có khai báo phần tên của hàm theo cú pháp chuẩn. Ta có thể đặt hàm xây dựng hoàn chỉnh ở bất kì vị trí nào. Cách khai báo hàm prototype có nhiều ưu điểm: - Không cần quan tâm đến thứ tự khai báo hàm. Nếu không sử dụng khai báo prototype, thì hàm khai báo sau mới được phép gọi hàm khai báo trước nó. Điều ngược lại là không được phép. Nhưng đối với khai báo prototype thì ta hoàn toàn không cần quan tâm đến điều này. - Ta có thể tách phần khai báo prototype và đặt nó vào trong một tập tin mới, thường là tập tin tiêu đề .h (với tên gọi tùy vào người lập trình quy định), phần thân hàm lại chứa trong một tệp khác, thường là .cpp hoặc trong chính tệp chứa chương trình chính. Cách làm này giúp chương trình sáng sủa hơn rất nhiều. Trong các dự án lập trình lớn, người ta thường phân tách theo dạng này. Chúng ta sẽ xét ví dụ minh họa sau. Trong ví dụ minh họa này, dự án của tôi gồm có hai tệp: tieude.h để chứa khai báo prototype và main.cpp để chứa thân hàm và hàm main. Đối với Codeblocks, hãy thực hiện theo các bước sau: - Tạo mới một dự án C++ và lưu lại. Trong dự án này, mặc định Codeblocks sẽ tạo một tệp main.cpp. - Vào New > File > chọn C/C++ header. Sau đó, hãy chọn vị trí để lưu trữ tệp tiêu đề (thông thường, ta nên tạo các thư mục khác nhau để lưu tệp .h cũng như tệp .cpp như tôi đã trình bày ở trên). Đối với Eclipse, thực hiện như sau: - Kích chuột phải vào thư mục cần đặt tệp .h, chọn New > Header File. - Kích chuột phải vào thư mục cần đặt tệp .cpp, chọn New > Source File. Đối với Visual Studio 2010, kích chuột phải vào tên dự án, chọn Add New Item. Sau đó chọn header file .h. Tệp tieude.h Tệp main.cpp #ifndef TIEUDE_H_INCLUDED #include #define TIEUDE_H_INCLUDED #include "tieude.h" using namespace std; int sum(int, int); int main() void showmsg(void); { showmsg(); #endif // TIEUDE_H_INCLUDED return 0; } void showmsg(){ cout<<sum(1, 3); C++ T r a n g | 64
  64. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ } int sum(int a, int b){ return a+b; } Trong tệp tieude.h, ta chỉ việc nhập các khai báo prototype vào giữa #define và #endif. Trong tệp main.cpp, ta cần bổ sung khai báo thư viện #include “tieude.h”. Chú ý rằng, tên tệp tiêu đề nằm trong dấu nháy kép “”, mà không phải là dấu - Biến global là biến toàn cục, nó có tác dụng trong toàn bộ chương using namespace std; trình. Ta có thể sử dụng nó trong int global; hàm main, hàm add int add(int a, int b) - Biến local, local1, local2 là các biến cục bộ. Biến local được khai C++ T r a n g | 65
  65. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ { báo trong hàm add, nó có phạm vi tác dụng trong phạm vi của hàm local result = a + b; này. Biến cục bộ local1 được khai return result; báo trong hàm main. Nó cũng chỉ có tác dụng trong hàm main. Biến } local2 được khai báo trong phạm vi tác dụng của câu lệnh if, nó chỉ có int main() tác dụng trong khối lệnh này. Nếu { ta gọi biến này ngoài khối lệnh của if, chương trình dịch sẽ báo lỗi. int local1; if(local1>0) { int local2; } return 0; } 1.7.3. Hàm không trả về giá trị - Hàm void. Như chúng ta đã thấy trong các ví dụ trên, các hàm mà chúng ta sử dụng là các hàm có giá trị trả về. Đối với các loại hàm này, để hàm trả về một giá trị nào đó, ta sử dụng từ khóa return. Giá trị hàm trả về phải có kiểu dữ liệu cùng loại với kiểu dữ liệu mà ta quy định khi khai báo hàm. Chúng ta cũng bắt gặp tình huống: nhiều lúc hàm mà ta xây dựng không trả về một giá trị nào, hoặc trả về nhiều hơn một giá trị. Khi đó, chúng ta sử dụng khai báo hàm void. Đối với hàm không trả về giá trị, ta có thể tham khảo ví dụ sau. Còn đối với hàm trả về nhiều hơn một giá trị, chúng ta sẽ thảo luận kĩ hơn trong phần tham biến. Ví dụ Kết quả #include Hello, world ! using namespace std; void showMsg() { cout<<”Hello, world !”; } C++ T r a n g | 66
  66. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ int main() { showMsg(); return 0; } Chú ý: Vì hàm có kiểu dữ liệu trả về luôn trả về một giá trị cụ thể, nên chúng ta có thể sử dụng trực tiếp loại hàm này trong các biểu thức tính toán (ví dụ a=b+sin(x)). Điều này là không thể đối với hàm void. Khi sử dụng các hàm không có tham số hình thức, nếu ta gọi hàm theo cách sau: tên_hàm(); là cách gọi hàm đúng. Nếu gọi hàm theo cách: tên_hàm;, thì dù chương trình dịch không báo lỗi, nhưng kết quả nhiều khi không chính xác. Vì vậy, chúng ta nên gọi hàm theo cách đầu tiên. 1.7.4. Tham biến và tham trị Cho đến thời điểm này, các hàm mà chúng ta đã nghiên cứu đều truyền tham số theo tham trị. Điều này có nghĩa là khi gọi hàm, các giá trị từ các đối số truyền vào trong hàm sẽ được sao chép sang các tham số này, các tham số đó chỉ đóng vai trò là các tham số hình thức, chúng không lưu lại giá trị cho các đối số truyền vào, mà các giá trị đó đã bị các lệnh trong hàm làm thay đổi. Ví dụ Giải thích #include Nếu tham số a trong hàm setNum được sử dụng như trên (đơn thuần là using namespace std; int a) thì nó được quy định là truyền void setNum(int a) theo tham trị. { Khi truyền theo tham trị, giá trị của biến xuất hiện trong lời gọi hàm này, a = 0; sẽ không thay đổi sau khi thoát ra khỏi hàm. Điều này có nghĩa là giá trị của } biến b trước khi gọi hàm là 1, sau khi int main() gọi hàm, nó vẫn nhận giá trị là 1. { int b = 1; setNum(b); cout<<b; C++ T r a n g | 67
  67. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ return 0; } Nếu muốn thay đổi giá trị của biến khi truyền tham số trong hàm, ta sử dụng khai báo tham biến. Với việc quy định các tham số truyền theo tham biến, thì khi khai báo ta chỉ bổ sung vào dấu & ở trước tên tham số đó. Bằng cách này, các biến là đối số trong lời gọi hàm sẽ bị làm thay đổi giá trị sau khi kết thúc lời gọi hàm. Ví dụ Giải thích #include Nếu tham số a trong hàm setNum được sử dụng như trên (int &a) thì nó được using namespace std; quy định là truyền theo tham biến. void setNum(int &a) Khi truyền theo tham biến, giá trị của { biến xuất hiện trong lời gọi hàm này, sẽ thay đổi sau khi thoát ra khỏi hàm. a = 0; Điều này có nghĩa là giá trị của biến b trước khi gọi hàm là 1, sau khi gọi } hàm, nó vẫn nhận giá trị là 0. int main() { int b = 1; setNum(b); cout m= 2, n=1 using namespace std; void swap(int &a, int &b) { C++ T r a n g | 68
  68. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ int c = a; a = b; b = c; } int main() { int m = 1; int n = 2; swap(m, n); cout m= 2, n=1 using namespace std; void swap(int *a, int *b) { int *c;//hoặc đơn thuần chỉ là c *c = *a; *a = *b; *b = *c; C++ T r a n g | 69
  69. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ } int main() { int m = 1; int n = 2; swap(&m, &n); cout 1 using namespace std; 3 int add(int a, int b=0, int c=0) 6 { return a+b+c; } int main() { cout<<add(1)<<endl; cout<<add(1,2)<<endl; cout<<add(1,2,3)<<endl; return 0; } C++ T r a n g | 70
  70. CHƯƠNG 1. NGÔN NGỮ LẬP TRÌNH C++ Giải thích: Hàm add được khai báo với ba tham số hình thức. Tham số thứ nhất là không thể thiếu, vì nó không quy định giá trị mặc định. Với hai tham số b, c còn lại, ta có thể để khuyết. Trong trường hợp để khuyết, nó sẽ nhận giá trị mặc định mà ta đã gán cho nó (cụ thể ở đây là 0). Do đó, khi gọi hàm add(1), nó sẽ tương ứng với lời gọi hàm add(1,0,0), tức giá trị là tổng của 1+0+0 bằng 1. Tương tự, khi gọi hàm add(1,2) thì sẽ tương ứng với add(1,2,0) và cho kết quả là 3. Khi gọi hàm đầy đủ ba tham số add(1,2,3) sẽ cho kết quả là 6. 1.7.6. Chồng chất hàm Trong C++, hai hàm khác nhau có thể có cùng tên, nhưng danh sách tham số của chúng phải khác nhau. Chúng được biết đến với tên gọi là chồng chất hàm. Khái niệm chồng chất hàm khác hoàn toàn với khái niệm quá tải hàm mà chúng ta sẽ tìm hiểu trong phần lập trình hướng đối tượng. Ví dụ Kết quả #include 3 #include abcd using namespace std; int add(int a, int b) { return a+b; } string add(string a, string b) { return a+b; } int main() { cout<<add(1,2)<<endl; cout<<add(“ab”,”cd”)<<endl; return 0; } C++ T r a n g | 71