Bài giảng môn học Ngôn ngữ lập trình bậc cao

doc 107 trang phuongnguyen 3650
Bạn đang xem 20 trang mẫu của tài liệu "Bài giảng môn học Ngôn ngữ lập trình bậc cao", để 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:

  • docbai_giang_mon_hoc_ngon_ngu_lap_trinh_bac_cao.doc

Nội dung text: Bài giảng môn học Ngôn ngữ lập trình bậc cao

  1. TRƯỜNG ĐẠI HỌC KỸ THUẬT CÔNG NGHIỆP KHOA ĐIỆN TỬ BỘ MÔN KỸ THUẬT PHẦN MỀM BÀI GIẢNG MÔN HỌC NGÔN NGỮ LẬP TRÌNH BẬC CAO Theo chương trình 150 TC Số tín chỉ: 03 (Lưu hành nội bộ) THÁI NGUYÊN 2010
  2. BỘ MÔN KỸ THUẬT PHẦN MỀM BÀI GIẢNG MÔN HỌC NGÔN NGỮ LẬP TRÌNH BẬC CAO Theo chương trình 150 TC Số tín chỉ: 03 (Lưu hành nội bộ) Thái Nguyên, ngày . tháng 12 năm 2010 TRƯỞNG BỘ MÔN TRƯỞNG KHOA ĐIỆN TỬ Ths. Nguyễn Thị Hương PGS. TS Nguyễn Hữu Công 1
  3. MỤC LỤC ĐỀ CƯƠNG CHI TIẾT HỌC PHẦN 5 CHƯƠNG 1. GIỚI THIỆU NGÔN NGỮ C++ 12 1.1. Lịch sử ngôn ngữ C++ 12 1.2. Cài đặt Borland C++ 4.5 13 CHƯƠNG 2. THÀNH PHẦN CƠ BẢN, KIỂU DỮ LIỆU CƠ SỞ VÀ PHÉP TOÁN .17 A. Phần lý thuyết 17 2.1. Các thành phần cơ bản 17 2.1.1. Bộ ký tự (Character Set) 17 2.1.2. Tên (Identifier) 17 2.1.3. Từ khoá (Keywords) 18 2.1.4. Lời giải thích (Comments) 19 2.1.5. Cấu trúc của một chương trình C++ 19 2.2. Các kiểu dữ liệu và cách khai báo 21 2.2.1. Khái niệm về kiểu dữ liệu 21 2.2.2. Kiểu dữ liệu cơ sở 21 2.2.3. Sự tương thích giữa các kiểu 24 2.2.4. Định nghĩa và khai báo hằng 24 2.2.5. Biến 27 2.2.6. Biến tham chiếu (reference) 29 2.2.7. Biến con trỏ (pointer) 30 2.2.8. Chuyển đổi kiểu dữ liệu 30 2.3. Biểu thức, câu lệnh và các phép toán 32 2.3.1. Biểu thức 32 2.3.2. Các phép toán 33 2.3.3. Thứ tự ưu tiên các phép toán 38 2.3.4. Câu lệnh 39 2.3.5. Một số hàm số học 40 B. Phần thảo luận, bài tập 42 CHƯƠNG 3. CÁC THAO TÁC XỬ LÝ INPUT/OUTPUT 43 A. Phần lý thuyết 43 3.1. Hàm in ra màn hình printf() và putchar() với các tham số 43 3.1.1. Hàm printf 43 3.1.2. Hàm putchar() 45 2
  4. 3.2. Hàm đọc ký tự từ bàn phím 45 3.3. Thực hiện Input/Output 45 3.3.1. Nhập dữ liệu 45 3.3.2. Xuất dữ liệu 48 3.4. Thiết lập khuôn dạng - Trình bày màn hình 48 3.4.1. Các phương thức định dạng 48 3.4.2. Cờ định dạng 50 3.4.3. Các phương thức bật tắt cờ 54 B. Phần thảo luận, bài tập 56 CHƯƠNG 4. CÁC CẤU TRÚC ĐIỀU KHIỂN 57 A. Phần lý thuyết 57 4.1. Cấu trúc if 57 4.2. Cấu trúc switch 59 4.3. Cấu trúc for 61 4.4. Cấu trúc while 63 4.5. Cấu trúc do while 65 4.6. Câu lệnh break 65 4.7. Câu lệnh continue 66 B. Phần thảo luận, bài tập 67 CHƯƠNG 5. HÀM TRONG C++ 68 A. Phần lý thuyết 68 5.1. Hàm trong C++ 68 5.2. Truyền tham số cho hàm 71 5.3. Đệ quy 74 5.3.1. Khái niệm đệ qui 74 5.3.2. Lớp các bài toán giải được bằng đệ qui 75 5.3.3. Cấu trúc chung của hàm đệ qui 76 5.4. Hàm inline 77 5.5. Hàm tải bội 78 B. Phần thảo luận, bài tập 79 CHƯƠNG 6. CÁC KIỂU DỮ LIỆU CÓ CẤU TRÚC 80 A. Phần lý thuyết 80 6.1. Mảng dữ liệu 80 6.1.1. Mảng một chiều 80 3
  5. 6.1.2. Mảng nhiều chiều 83 6.2. Xâu ký tự 87 6.2.1. Khai báo 88 6.2.2. Cách sử dụng 88 6.2.3. Phương thức nhập xâu 89 6.2.4. Một số hàm xử lí xâu 90 6.3. Cấu trúc (structure) 96 6.3.1. Khai báo 96 6.3.2. Truy nhập các thành phần kiểu cấu trúc 98 6.3.3. Phép toán gán cấu trúc 99 6.4. Cấu trúc động của dữ liệu (Union) 101 6.5. Các kiểu dữ liệu tự định nghĩa khác 103 B. Phần thảo luận, bài tập 105 4
  6. ĐỀ CƯƠNG CHI TIẾT HỌC PHẦN ĐẠI HỌC THÁI NGUYÊN CỘNG HOÀ XÃ HỘI CHỦ NGHĨA VIỆT NAM TRƯỜNG ĐẠI HỌC Độc lập - Tự do - Hạnh phúc KỸ THUẬT CÔNG NGHIỆP CHƯƠNG TRÌNH GIÁO DỤC ĐẠI HỌC NGÀNH ĐÀO TẠO: ĐIỆN TỬ CHUYÊN NGÀNH: CÁC KHỐI NGÀNH KỸ THUẬT ĐỀ CƯƠNG CHI TIẾT HỌC PHẦN NGÔN NGỮ LẬP TRÌNH BẬC CAO (HỌC PHẦN BẮT BUỘC) 1. Tên học phần: Ngôn ngữ lập trình bậc cao 2. Số tín chỉ: 03; 3(3;1,5;6)/12 3. Trình độ: 4. Phân bổ thời gian: - Lên lớp lý thuyết: 3 (tiết/tuần) x 12 (tuần) = 36 tiết. - Thảo luận, thực hành: 1,5 (tiết/tuần) x 12 (tuần) = 18 tiết. + Thảo luận: 10 tiết + Thực hành: 8 tiết - Hướng dẫn bài tập lớn (dài): - Khác: Không. - Tổng số tiết thực dạy: (3+1,5)x12 = 54 tiết thực hiện. - Tổng số tiết chuẩn: 3x12+1,5x12/2 = 45 tiết chuẩn. 5. Các học phần học trước: Toán cao cấp 6. Học phần thay thế, học phần tương đương: Không 7. Mục tiêu của học phần: Trang bị cho sinh viên kiến thức nâng cao trong lĩnh vực tin học, cụ thể: giúp cho sinh viên nắm chắc được quy trình xây dựng chương trình để giải quyết một bài toán cụ thể, đặc biệt là trong lĩnh vực kỹ thuật. Từ khâu đặt vấn đề của bài toán, phân tích yêu cầu của bài toán, xây dựng thuật toán, mã hóa chương trình trên ngôn ngữ bậc cao (C++), kiểm thử và khai thác sử dụng. 8. Mô tả vắn tắt nội dung học phần: Môn học cung cấp các kiến thức chi tiết về ngôn ngữ lập trình C++ nhằm giải quyết các bài toán kỹ thuật. Cụ thể: - Các thành phần của ngôn ngữ. - Cấu trúc của một chương trình C++. - Biến và các kiểu dữ liệu đơn giản trong C++. 5
  7. - Biểu thức, câu lệnh và các phép toán. - Câu lệnh đơn giản và câu lệnh có cấu trúc. - Hàm, đệ quy và truyền tham số. - Các kiểu dữ liệu có cấu trúc: mảng, xâu, cấu trúc, file. 9. Nhiệm vụ của sinh viên: -Dự lớp 80 % tổng số thời lượng của học phần. - Làm bài tập ở nhà. - Chuẩn bị thảo luận 10. Tài liệu học tập: - Sách, giáo trình chính: [1]. Tống Đình Quỳ, Ngôn ngữ lập trình C++, NXB Thống kê 2000. [2]. Tống Đình Quỳ, Bài tập ngôn ngữ lập trình C++, NXB Thống kê 2000. - Tài liệu tham khảo: [3]. Quách Tuấn Ngọc, Ngôn ngữ lập trình C, NXB Giáo Dục, 1998. [4]. GS. Phạm Văn Ất, Kỹ thuật lập trình C, NXB KH&KT, 1999. [5]. Leendert Ammeraal, Programs and Data Structures in C, John Willey & Sons Press. [6]. N. Wirth, Cẩm nang lập trình tập 1, tập 2, NXB Thống kê 1981. [7]. Đỗ Xuân Lôi, Cấu trúc dữ liệu và giải thuật, NXB Thống kê 1996. 11. Tiêu chuẩn đánh giá sinh viên: - Dự lớp: ≥ 80% tổng số giờ môn học. - Thảo luận. - Kiểm tra giữa học phần. - Thi kết thúc học phần. * Thang điểm - Thực hành: Trọng số 0.1 - Kiểm tra giữa học phần: Trọng số 0.2 - Thi kết thúc học phần: Trọng số 0.8 12. Nội dung chi tiết học phần: - Người biên soạn: KS Võ Phúc Nguyên KS Đỗ Duy Cốp ThS Nguyễn Tuấn Anh 6
  8. CHƯƠNG 1. GIỚI THIỆU NGÔN NGỮ C++ (Tổng số tiết 1; Lý thuyết 1) 1.1. Lịch sử ngôn ngữ C và C++ 1.2. Cài đặt C++ 1.3. Môi trường Borland C++ 1.4. Thiết lập cấu hình cho môi trường CHƯƠNG 2. CÁC THÀNH PHẦN CƠ BẢN, CÁC KIỂU DỮ LIỆU CƠ SỞ VÀ CÁC PHÉP TOÁN (Tổng số tiết 9; Lý thuyết 6; Thảo luận 2) 2.1. Các thành phần cơ bản 2.1.1. Bộ ký tự 2.1.2. Tên 2.1.3. Từ khoá 2.1.4. Lời giải thích 2.1.5. Cấu trúc của một chương trình C++ và quy tắc viết chương trình 2.2. Các kiểu dữ liệu và cách khai báo 2.2.1. Kiểu dữ liệu cơ sở 2.2.1.1. Kiểu số nguyên 2.2.1.2. Kiểu số thực 2.2.1.3. Kiểu ký tự 2.2.2. Sự tương thích giữa các kiểu 2.2.3. Định nghĩa và khai báo hằng 2.2.4. Các biến tham chiếu 2.2.5. Biến con trỏ 2.3. Biểu thức, câu lệnh và các phép toán 2.3.1. Biểu thức và các phép toán 2.3.2. Thứ tự thực hiện các phép toán 2.3.3. Câu lệnh 2.3.4. Lệnh hợp thành 2.3.5. Một số hàm số học CHƯƠNG 3. CÁC THAO TÁC XỬ LÝ INPUT/OUTPUT (Tổng số tiết 6; Lý thuyết 3; Thảo luận 2 Thực hành 1) 3.1. Hàm in ra màn hình printf() 3.2. Hàm đọc ký tự từ bàn phím scanf() 3.3. Thực hiện Input/Output với dòng tin trong C++ 7
  9. 3.3.1. Input 3.3.2. Output 3.4. Thiết lập khuôn dạng - trình bày màn hình CHƯƠNG 4. CÁC CẤU TRÚC ĐIỀU KHIỂN (Tổng số tiết 15; Lý thuyết 12; Thảo luận 2, Thực hành 1) 4.1. Cấu trúc if 4.2. Cấu trúc switch 4.3. Cấu trúc for 4.4. Cấu trúc while 4.5. Cấu trúc do 4.6. Câu lệnh break 4.7. Câu lệnh continue CHƯƠNG 5. HÀM TRONG C++ (Tổng số tiết 8; Lý thuyết 5; Thảo luận 2, Thực hành 1) 5.1. Hàm trong C++ 5.2. Truyền tham số cho hàm 5.3. Đệ quy 5.4. Hàm inline CHƯƠNG 6. CÁC KIỂU DỮ LIỆU CÓ CẤU TRÚC (Tổng số tiết 15; Lý thuyết 12; Thảo luận 2, Thực hành 1) 6.1. Mảng dữ liệu 6.1.1. Mảng một chiều 6.1.2. Mảng nhiều chiều 6.2. Xâu ký tự và các hàm xử lý xâu 6.3. Cấu trúc (structure) 6.4. Cấu trúc động của dữ liệu (union) 6.5. Các kiểu dữ liệu tự định nghĩa khác 13. Lịch trình giảng dạy - Số tuần dạy lý thuyết: 08 tuần - Số tuần thảo luận, bài tập: 04 tuần - Số tuần thực dạy: 12 tuần + 6 Tuần 5 tiết/tuần (4 tuần lý thuyết, 2 tuần thảo luận) + 6 Tuần 4 tiết/tuần (4 tuần lý thuyết, 2 tuần thực hành) 8
  10. Tuần Tài liệu học Hình thức Nội dung thứ tập, tham khảo học CHƯƠNG 1. GIỚI THIỆU NGÔN NGỮ C++ 1.1. Lịch sử ngôn ngữ C và C++ 1.2. Cài đặt C++ 1.3. Môi trường Borland C++ 1.4. Thiết lập cấu hình cho môi trường CHƯƠNG 2. CÁC THÀNH PHẦN CƠ BẢN, CÁC KIỂU DỮ LIỆU CƠ SỞ VÀ CÁC PHÉP TOÁN 2.1. Các thành phần cơ bản 2.1.1. Bộ ký tự 2.1.2. Tên 2.1.3. Từ khoá 1 2.1.4. Lời giải thích [1] - [8] Giảng 2.1.5. Cấu trúc của một chương trình C++ và quy tắc viết chương trình 2.2. Các kiểu dữ liệu và cách khai báo 2.2.1. Kiểu dữ liệu cơ sở 2.2.1.1. Kiểu số nguyên 2.2.1.2. Kiểu số thực 2.2.1.3. Kiểu ký tự 2.2.2. Sự tương thích giữa các kiểu 2.2.3. Định nghĩa và khai báo hằng 2.2.4. Các biến tham chiếu 2.2.5. Biến con trỏ 2.3. Biểu thức, câu lệnh và các phép toán 2.3.1. Biểu thức và các phép toán 2.3.2. Thứ tự thực hiện các phép toán 2.3.3. Câu lệnh 2.3.4. Lệnh hợp thành 2 2.3.5. Một số hàm số học [1] - [8] Giảng CHƯƠNG 3. CÁC THAO TÁC XỬ LÝ INPUT/OUTPUT 3.1. Hàm in ra màn hình printf() 3.2. Hàm đọc ký tự từ bàn phím scanf() 3.3. Thực hiện Input/Output với dòng tin trong C++ 9
  11. 3.3.1. Input 3.3.2. Output 3.4. Thiết lập khuôn dạng - trình bày màn hình CHƯƠNG 4. CÁC CẤU TRÚC ĐIỀU KHIỂN 3 4.1. Cấu trúc if [1] - [8] Giảng 4.2. Cấu trúc switch 4.3. Cấu trúc for 4.4. Cấu trúc while 4.5. Cấu trúc do 4 [1] - [8] Giảng 4.6. Câu lệnh break 4.7. Câu lệnh continue CHƯƠNG 5. HÀM TRONG C++ 5.1. Hàm trong C++ 5 5.2. Truyền tham số cho hàm [1] - [8] Thảo luận 5.3. Đệ quy 5.4. Hàm inline Thảo luận và làm các bài tập từ chương 1 6 [1] - [8] Thảo luận đến chương 4 7 Kiểm tra giữa kỳ CHƯƠNG 6. CÁC KIỂU DỮ LIỆU CÓ CẤU TRÚC 8 6.1. Mảng dữ liệu [1] - [8] Giảng 6.1.1. Mảng một chiều 6.1.2. Mảng nhiều chiều 6.2. Xâu ký tự và các hàm xử lý xâu 9 [1] - [8] Giảng 6.3. Cấu trúc (structure) 6.4. Cấu trúc động của dữ liệu (union) 10 [1] - [8] Giảng 6.5. Các kiểu dữ liệu tự định nghĩa khác 11 Thực hành [1] - [8] Phòng máy 12 Thực hành [1] - [8] Phòng máy Thảo luận và làm các bài tập từ chương 5 13 [1] - [8] Giảng đến chương 6 14. Ngày phê duyệt: 15. Cấp phê duyệt: 10
  12. Đề cương chi tiết học phần đã được Hội đồng khối ngành Điện – Điện tử và SPKT Điện – Tin học phê duyệt. Trưởng bộ môn Chủ tịch Hội đồng Chủ tịch Hội đồng Kỹ thuật phần mềm KH&GD Khoa Điện tử Khối ngành Điện - Điện tử và SPKT Điện – Tin học ThS. Nguyễn Thị Hương PGS.TS. Nguyễn Hữu Công PGS.TS Nguyễn Như Hiển 11
  13. CHƯƠNG 1. GIỚI THIỆU NGÔN NGỮ C++ 1.1. Lịch sử ngôn ngữ C++ Lịch sử ngôn ngữ lập trình C: là một ngôn ngữ mệnh lệnh được phát triển từ đầu thập niên 1970 bởi Ken Thompson và Dennis Ritchie tại phòng thí nghiệm Bell Telephone để dùng trong hệ điều hành UNIX; theo Ritchie thì thời gian sáng tạo nhất là vào năm 1972. Nó được đặt tên là C vì nhiều đặc tính của nó rút ra từ một ngôn ngữ trước đó là B. Từ dó, ngôn ngữ này đã lan rộng ra nhiều hệ điều hành khác và trở thành một những ngôn ngữ phổ dụng nhất. C là ngôn ngữ rất có hiệu quả và được ưa chuộng nhất để viết các phần mềm hệ thống, mặc dù nó cũng được dùng cho việc viết các ứng dụng. Ngoài ra, C cũng thường được dùng làm phương tiện giảng dạy trong khoa học máy tính mặc dù ngôn ngữ này không dược thiết kế dành cho người nhập môn. C++ là gì? Có thể thấy rằng ngôn ngữ lập trình C được phát triển đầu tiên sau đó C++ mới được phát triển. Vậy C++ nó là cái gì? Nó có mối quan hệ thế nào với C. Câu trả lời là: C++ cơ bản là C ở một mức độ mới. Sự khác biệt quan trọng duy nhất là C++ hỗ trợ hướng đối tượng. Các đoạn mã viết bằng C được dịch và chạy tốt với hầu hết các chương trình dịch của C++ nhưng điều ngược lại không đúng. C++ hỗ trợ tất cả các lệnh của C và có mở rộng. Lịch sử ngôn ngữ lập trình C++: Bjarne Stroustrup của Bell Labs đã phát triển C++ (mà tên nguyên thủy là "C với các lớp" trong suốt thập niên 1980 như là một bản nâng cao của ngôn ngữ C. Những bổ sung nâng cao bắt đầu với sự thêm vào của khái niệm lớp, tiếp theo đó là các khái niệm hàm ảo, toán tử quá tải, đa kế thừa, tiêu bản, và xử lý ngoại lệ. Tiêu chuẩn của ngôn ngữ C++ đã được thông qua trong năm 1998 như là ISO/IEC 14882: 1998. Phiên bản hiện đang lưu hành là phiên bản 2003, ISO/IEC 14882: 2003. Năm 1983, thì tên C với các lớp được đổi thành C++. các chức năng mới được thêm vào bao gồm hàm ảo, quá tải hàm và toán tử, tham chiếu, hằng, khả năng kiểm soát bộ nhớ của lưu trữ tự do, nâng cao việc kiểm soát kiểu, và lệnh chú giải kiểu (//). Năm 1989 phiên bản C++ 2.0 phát hành. Các tính năng mới bao gồm đa kế thừa, lớp trừu tượng, hàm tĩnh, hàm thành viên hằng, và thành viên bảo tồn. Phiên bản xuất bản sau đó có thêm các chức năng tiêu bản, ngoại lệ, không gian tên, chuyển kiểu cho toán tử new, và kiểu Boolean. 12
  14. Khi C++ hình thành, thì thư viện chuẩn hoàn thiện với nó. Thư viện C++ đầu tiên thêm vào là IOSttream cung cấp cơ sở để thay thế các hàm C truyền thống như là printf và scanf. Sau này, trong những thư viện chuẩn quan trọng nhất được thêm vào là Thư viện Tiêu bản Chuẩn. Sau nhiều năm làm việc, có sự cộng tác giữa ANSI và hội đồng tiêu chuẩn hoá C++ của ISO để soạn thảo tiêu chuẩn ISO/IEC 14882: 1998. Phiên bản tiêu chuẩn này được phát hành năm 1989, hội đồng tiếp tục xử lí các báo cáo trục trặc, và ấn hành một phiên bản sửa sai của chuẩn C++ trong năm 2003. Không ai là chủ nhân của ngôn ngữ C++, nó hoàn toàn miễn phí khi dùng. Mặc dù vậy, các văn bản tiêu chuẩn thì không miễn phí. C++ về bản chất được xây dựng trên nền của ngôn ngữ lập trình C. C++ nguyên là sự kết thừa từ C. Mặc dù vậy, không phải mọi chương trình trong C đều hợp lệ trong C++. Vì là hai ngôn ngữ độc lập, số lượng không tương thích giữa hai ngôn ngữ này đã tăng lên. [2]. Phiên bản cuối cùng C99 đã tạo ra thêm nhiều tính năng xung đột (giữa C và C++). Các sự khác nhau này tạo ra khó khăn để viết các chương trình và thư viện để có thể được dịch và hoạt động chính xác trong cả hai loại mã C hay C++, đồng thời gây nhầm lẫn cho những người lập trình dùng cả hai ngôn ngữ này. Sự chênh lệch này cũng gây khó khăn cho ngôn ngữ này có thể tiếp thu các tính năng của ngôn ngữ kia. 1.2. Cài đặt Borland C++ 4.5 Bước 1: Chạy file install.exe 13
  15. Bước 2: Chọn nút Continue Bước 3: Chọn nút Continue Bước 4: Chọn nút Continue 14
  16. Bước 5: Chọn nút Yes Bước 6: Chọn nút Continue Bước 7: Chọn nút Install Bước 8: Chọn nút Continue 15
  17. Bước 9: Sau khi chọn nút OK ta đã cài xong chương trình Borland C++ 4.5. Giao diện chương trình Borland C++ 4.5 16
  18. CHƯƠNG 2. THÀNH PHẦN CƠ BẢN, KIỂU DỮ LIỆU CƠ SỞ VÀ PHÉP TOÁN A. Phần lý thuyết 2.1. Các thành phần cơ bản 2.1.1. Bộ ký tự (Character Set) Mọi ngôn ngữ lập trình đều được xây dựng từ một bộ ký tự nào đó. Các ký tự được nhóm lại theo nhiều cách khác nhau để tạo nên các từ. Các từ lại được liên kết với nhau theo một qui tắc nào đó để tạo nên các câu lệnh. Một chương trình bao gồm nhiều câu lệnh và thể hiện một thuật toán để giải một bài toán nào đó. Ngôn ngữ C++ được xây dựng trên bộ ký tự sau: - 26 chữ cái hoa: A B C Z - 26 chữ cái thường: a b c z - 10 chữ số: 0 1 2 9 - Các ký hiệu toán học: + - * / =() - Ký tự gạch nối: _ - Các ký tự khác:.,: ; [ ] {} ! \ & % # $ Dấu cách (space) dùng để tách các từ. Ví dụ chữ VIET NAM có 8 ký tự, còn VIETNAM chỉ có 7 ký tự. Chú ý: Khi viết chương trình, ta không được sử dụng bất kỳ ký tự nào khác ngoài các ký tự trên. Ví dụ như khi lập chương trình giải phương trình bậc hai ax 2 +bx+c=0, ta cần tính biệt thức Delta = b2 - 4ac, trong ngôn ngữ C++ không cho phép dùng ký tự , vì vậy ta phải dùng ký hiệu khác để thay thế. 2.1.2. Tên (Identifier) Tên là một khái niệm rất quan trọng, nó dùng để xác định các đại lượng khác nhau trong một chương trình. Chúng ta có tên hằng, tên biến, tên mảng, tên hàm, tên con trỏ, tên tệp, tên cấu trúc, tên nhãn, Tên được đặt theo qui tắc sau: 17
  19. Tên là một dãy các ký tự bao gồm chữ cái, số và gạch nối. Ký tự đầu tiên của tên phải là chữ hoặc gạch nối. Tên không được trùng với khoá. Độ dài cực đại của tên theo mặc định là 32 và có thể được đặt lại là một trong các giá trị từ 1 tới 32 nhờ chức năng: Option-Compiler-Source-Identifier length khi dùng TURBO C++. Các tên đúng: a_1 delta x1 _step GAMA Các tên sai: 3MN Ký tự đầu tiên là số m#2 Sử dụng ký tự # f(x) Sử dụng các dấu () do Trùng với từ khoá te ta Sử dụng dấu trắng Y-3 Sử dụng dấu - Chú ý: Trong TURBO C++, tên bằng chữ thường và chữ hoa là khác nhau ví dụ tên AB khác với ab. Trong C++, ta thường dùng chữ hoa để đặt tên cho các hằng và dùng chữ thường để đặt tên cho hầu hết cho các đại lượng khác như biến, biến mảng, hàm, cấu trúc. Tuy nhiên đây không phải là điều bắt buộc. 2.1.3. Từ khoá (Keywords) Từ khoá là những từ được sử dụng để khai báo các kiểu dữ liệu, để viết các toán tử và các câu lệnh. Bảng dưới đây liệt kê các từ khoá của TURBO C++: asm break case cdecl char const continue default do double else enum extern far float for goto huge if int interrupt long near pascal register return short signed sizeof static struct switch tipedef union unsigned void volatile while Chú ý: - Không được dùng các từ khoá để đặt tên cho các hằng, biến, mảng, hàm 18
  20. - Từ khoá phải được viết bằng chữ thường, ví dụ: viết từ khoá khai báo kiểu nguyên là int chứ không phải là int. 2.1.4. Lời giải thích (Comments) Các chú thích được các lập trình viên sử dụng để ghi chú hay mô tả trong các phần của chương trình. Trong C++ có hai cách để chú thích: //Chú thích theo dòng /*Chú thích theo khối Dòng chú thích 1 Dòng chú thích 2 */ Chú thích theo dòng bắt đầu từ cặp dấu xổ (//) cho đến cuối dòng. Chú thích theo khối bắt đầu bằng /*và kết thúc bằng */ và có thể bao gồm nhiều dòng. Chúng ta sẽ thêm các chú thích cho chương trình: Ví dụ 2.1 //Ví dụ về ghi chú trong C++ #include void main(){ //In ra màn hình dòng Hello World! cout int main() { cout<<"Hello World!"; return 0; } Chương trình trên đây là chương trình đầu tiên mà hầu hết những người học nghề lập trình viết đầu tiên và kết quả của nó là viết câu "Hello, World" lên màn hình. Đây là một trong những chương trình đơn giản nhất có thể viết bằng C++ nhưng nó 19
  21. đã bao gồm những phần cơ bản mà mọi chương trình C++ có. Hãy cùng xem xét từng dòng một: //My first program in C++ Đây là dòng chú thích. Tất cả các dòng bắt đầu bằng hai dấu sổ (//) được coi là chút thích mà chúng không có bất kì một ảnh hưởng nào đến hoạt động của chương trình. Chúng có thể được các lập trình viên dùng để giải thích hay bình phẩm bên trong mã nguồn của chương trình. Trong trường hợp này, dòng chú thích là một giải thích ngắn gọn những gì mà chương trình chúng ta làm. #include Câu lệnh #include báo cho trình dịch biết cần phải "include" thư viện iostream. Đây là một thư viện vào ra cơ bản trong C++ và nó phải được "include" vì nó sẽ được dùng trong chương trình. Đây là cách cổ điển để sử dụng thư viện iostream int main() Dòng này tương ứng với phần bắt đầu khai báo hàm main. Hàm main là điểm mà tất cả các chương trình C++ bắt đầu thực hiện. Nó không phụ thuộc vào vị trí của hàm này (ở đầu, cuối hay ở giữa của mã nguồn) mà nội dung của nó luôn được thực hiện đầu tiên khi chương trình bắt đầu. Thêm vào đó, do nguyên nhân nói trên, mọi chương trình C++ đều phải tồn tại một hàm main. Theo sau main là một cặp ngoặc đơn bởi vì nó là một hàm. Trong C++, tất cả các hàm mà sau đó là một cặp ngoặc đơn () thì có nghĩa là nó có thể có hoặc không có tham số (không bắt buộc). Nội dung của hàm main tiếp ngay sau phần khai báo chính thức được bao trong các ngoặc nhọn ({ }) như trong ví dụ của chúng ta. cout<<"Hello World"; Dòng lệnh này làm việc quan trọng nhất của chương trình. cout là một dòng (stream) output chuẩn trong C++ được định nghĩa trong thư viện iostream và những gì mà dòng lệnh này làm là gửi chuỗi kí tự "Hello World" ra màn hình. Chú ý rằng dòng này kết thúc bằng dấu chấm phẩy (;). Kí tự này được dùng để kết thúc một lệnh và bắt buộc phải có sau mỗi lệnh trong chương trình C++ của bạn (một trong những lỗi phổ biến nhất của những lập trình viên C++ là quên mất dấu chấm phẩy). return 0; 20
  22. Lệnh return kết thúc hàm main và trả về mã đi sau nó, trong trường hợp này là 0. Đây là một kết thúc bình thường của một chương trình không có một lỗi nào trong quá trình thực hiện. Như bạn sẽ thấy trong các ví dụ tiếp theo, đây là một cách phổ biến nhất để kết thúc một chương trình C++. Chương trình được cấu trúc thành những dòng khác nhau để nó trở nên dễ đọc hơn nhưng hoàn toàn không phải bắt buộc phải làm vậy. Ví dụ, thay vì viết int main() { cout<<" Hello World "; return 0; } ta có thể viết int main() { cout<<" Hello World "; return 0; } cũng cho một kết quả chính xác như nhau. Trong C++, các dòng lệnh được phân cách bằng dấu chấm phẩy (Việc chia chương trình thành các dòng chỉ nhằm để cho nó dễ đọc hơn mà thôi). 2.2. Các kiểu dữ liệu và cách khai báo 2.2.1. Khái niệm về kiểu dữ liệu C++ chia thành hai tập hợp kiểu dữ liệu chính: Kiểu xây dựng sẵn (built- in) mà ngôn ngữ cung cấp cho người lập trình và kiểu được người dùng định nghĩa (user-defined) do người lập trình tạo ra. 2 tập hợp kiểu dữ liệu này phân làm 2 loại kiểu dữ liệu giá trị (value) và kiểu dữ liệu tham chiếu(reference). Trong đó Tất cả các kiểu dữ liệu xây dựng sẵn là kiểu dữ liệu giá trị ngoại trừ các đối tượng và chuỗi. Và tất cả các kiểu do người dùng định nghĩ ngoại trừ kiểu cấu trúc đều là kiểu dữ liệu tham chiếu. Các đối tượng thuộc 1 kiểu dữ liệu có thể được chuyển đổi sang một kiểu dữ liệu khác qua cơ chế chuyển đổi tường minh hoặc chuyển đổi ngầm định. Việc chuyển đổi ngầm định được thực hiện tự động và phải đảm bảo không mất thông tin. Nghĩa là kiểu được chuyển đổi phải có kích thước lơn hơn. Ta có thể gán ngầm định một số kiểu short vào một số kiểu int nhưng không thể làm ngược lại vì kích thước của kiểu int lớn hơn kích thước của kiểu short. 2.2.2. Kiểu dữ liệu cơ sở  Kiểu số nguyên 21
  23. Trong C++ cho phép sử dụng số nguyên kiểu int, số nguyên dài kiểu long và số nguyên không dấu kiểu unsigned. Kích cỡ và phạm vi biểu diễn của chúng được chỉ ra trong bảng dưới đây: Kiểu Phạm vi biểu diễn Kích thước unsigned int 0 đến 65535 2 byte Short int -32768 đến 32767 2 byte int -32768 đến 32767 2 byte long -2147483648 đến 2147483647 4 byte unsigned long 0 đến 4294967295 4 byte Chú ý: Kiểu ký tự cũng có thể xem là một dạng của kiểu nguyên.  Kiểu số thực Trong C++ cho phép sử dụng ba loại dữ liệu dấu phảy động, đó là float, double và long double. Kích cỡ và phạm vi biểu diễn của chúng được chỉ ra trong bảng dưới đây: Số chữ số Kiểu Phạm vi biểu diễn Kích thước có nghĩa float 3.4E-38 đến 3.4E+38 7 đến 8 4 byte double 1.7E-308 đến 1.7E+308 15 đến 16 8 byte 3.4E-4932 đến long double 17 đến 18 10 byte 1.1E4932 Giải thích: Máy tính có thể lưu trữ được các số kiểu float có giá trị tuyệt đối từ 3.4E-38 đến 3.4E+38. Các số có giá trị tuyệt đối nhỏ hơn3.4E-38 được xem bằng 0. Phạm vi biểu diễn của số double được hiểu theo nghĩa tương tự.  Kiểu ký tự Một giá trị kiểu char chiếm 1 byte (8 bit) và biểu diễn được một ký tự thông qua bảng mã ASCII. 22
  24. Ví dụ 2.3 Ký tự Mã ASCII 0 048 1 049 2 050 A 065 B 066 A 097 B 098 Có hai kiểu dữ liệu char: kiểu signed char và unsigned char. Kiểu Phạm vi biểu diễn Số ký tự Kích thước char (signed char) -128 đến 127 256 1 byte unsigned char 0 đến 255 256 1 byte Ví dụ sau minh hoạ sự khác nhau giữa hai kiểu dữ liệu trên: Xét đoạn chương trình sau: char ch1; unsigned char ch2; ch1=200; ch2=200; Khi đó thực chất: ch1=-56; ch2=200; Nhưng cả ch1 và ch2 đều biểu diễn cùng một ký tự có mã 200. Có thể chia 256 ký tự làm ba nhóm: Nhóm 1: Nhóm các ký tự điều khiển có mã từ 0 đến 31. Chẳng hạn ký tự mã 13 dùng để chuyển con trỏ về đầu dòng, ký tự 10 chuyển con trỏ xuống dòng dưới (trên cùng một cột). Các ký tự nhóm này nói chung không hiển thị ra màn hình. Nhóm 2: Nhóm các ký tự văn bản có mã từ 32 đến 126. Các ký tự này có thể được đưa ra màn hình hoặc máy in. Nhóm 3: Nhóm các ký tự đồ hoạ có mã số từ 127 đến 255. Các ký tự này có thể đưa ra màn hình nhưng không in ra được (bằng các lệnh DOS).  Biểu boolean C++ không có kiểu Boolean xây dựng sẵn. Vì vậy ta có thể sử dụng kiểu int cho mục đích này với “0” là false và các số khác là true. 23
  25. 2.2.3. Sự tương thích giữa các kiểu Việc chuyển đổi một kiểu đã xác định thành một kiểu khác về cơ bản sẽ thay đổi một hay cả hai tính chất của kiểu nhưng không làm thay đổi hình mẫu bit nền tảng. Kích cỡ kiểu có thể rộng hơn hay hẹp hơn và tất nhiên cách hiểu về kiểu sẽ thay đổi. Một số phép chuyển đổi là không an toàn, về cơ bản trình biên dịch sẽ cảnh báo điều đó, việc chuyển đổi từ kiểu dữ liệu rộng hơn sang một kiểu dữ liệu hẹp hơn là một hình thái chuyển đổi kiểu không an toàn. 2.2.4. Định nghĩa và khai báo hằng a) Định nghĩa và khai báo hằng Định nghĩa hằng: Bạn có thể định nghĩa các hằng với tên mà bạn muốn để có thể sử dụng thường xuyên mà không mất tài nguyên cho các biến bằng cách sử dụng chỉ thị #define. Đây là dạng của nó: #define [giá trị] Ví dụ 2.4 #define PI 3.14159265 #define NEWLINE '\n' #define WIDTH 100 Chúng định nghĩa ba hằng số mới. Sau khi khai báo bạn có thể sử dụng chúng như bất kì các hằng số nào khác, ví dụ: circle = 2 * PI * r; cout<<NEWLINE; Trong thực tế việc duy nhất mà trình dịch làm khi nó tìm thấy một chỉ thị #define là thay thế các tên hằng tại bất kì chỗ nào chúng xuất hiện (như trong ví dụ trước, PI, NEWLINE hay WIDTH) bằng giá trị mà chúng được định nghĩa. Chỉ thị #define không phải là một lệnh thực thi, nó là chỉ thị tiền xử lý (preprocessor), đó là lý do trình dịch coi cả dòng là một chỉ thị và dòng đó không cần kết thúc bằng dấu chấm phẩy. Nếu bạn thêm dấu chấm phẩy vào cuối dòng, nó sẽ được coi là một phần của giá trị định nghĩa hằng. Khai báo các hằng (const): Với tiền tố const bạn có thể khai báo các hằng với một kiểu xác định như là bạn làm với một biến: const int width = 100; 24
  26. const to char tab = '\t'; const zip = 12440; b) Các loại hằng Hằng int: Hằng int là số nguyên có giá trị trong khoảng từ -32768 đến 32767. Ví dụ 2.5 #define number1 -50 Định nghiã hằng int number1 có giá trị là -50 #define sodem 2732 Định nghiã hằng int sodem có giá trị là 2732 Chú ý: Cần phân biệt hai hằng 5056 và 5056.0: ở đây 5056 là số nguyên còn 5056.0 là hằng thực. Hằng long: Hằng long là số nguyên có giá trị trong khoảng từ -2147483648 đến 2147483647. Hằng long được viết theo cách: 1234L hoặc 1234l (thêm L hoặc l vào đuôi) Một số nguyên vượt ra ngoài miền xác định của int cũng được xem là long. Ví dụ 2.6 #define sl 8865056L Định nghĩa hằng long sl có giá trị là 8865056 #define sl 8865056 Định nghiã hằng long sl có giá trị là 8865056 Hằng ký tự: Hằng ký tự là một ký tự riêng biệt được viết trong hai dấu nháy đơn, ví dụ 'a'. Giá trị của 'a' chính là mã ASCII của chữ a. Như vậy giá trị của 'a' là 97. Hằng ký tự có thể tham gia vào các phép toán như mọi số nguyên khác. Ví dụ: '9'-'0'=57-48=9 Chú thích: #define kt 'a' Định nghĩa hằng ký tự kt có giá trị là 97 Hằng ký tự còn có thể được viết theo cách sau: '\c1c2c3', trong đó c1c2c3 là một số hệ 8 mà giá trị của nó bằng mã ASCII của ký tự cần biểu diễn. 25
  27. Ví dụ: chữ a có mã hệ 10 là 97, đổi ra hệ 8 là 0141. Vậy hằng ký tự 'a' có thể viết dưới dạng '\141'. Đối với một vài hằng ký tự đặc biệt ta cần sử dụng cách viết sau (thêm dấu \): Cách viết Ký tự '\'' ' '\"' " '\\' \ '\n' \n (chuyển dòng) '\0' \0 (null) '\t' Tab '\b' Backspace '\r' CR (về đầu dòng) '\f' LF (sang trang) Chú ý: Cần phân biệt hằng ký tự '0' và '\0'. Hằng '0' ứng với chữ số 0 có mã ASCII là 48, còn hằng '\0' ứng với ký tự \0 (thường gọi là ký tự null) có mã ASCII là 0. Hằng ký tự thực sự là một số nguyên, vì vậy có thể dùng các số nguyên hệ 10 để biểu diễn các ký tự, ví dụ lệnh printf("%c%c",65,66) sẽ in ra AB. Hằng xâu ký tự: Hằng xâu ký tự là một dãy ký tự bất kỳ đặt trong hai dấu nháy kép. Ví dụ 2.7 #define xau1 "Ha noi" #define xau2 "My name is Giang" Xâu ký tự được lưu trữ trong máy dưới dạng một bảng có các phần tử là các ký tự riêng biệt. Trình biên dịch tự động thêm ký tự null \0 vào cuối mỗi xâu (ký tự \0 được xem là dấu hiệu kết thúc của một xâu ký tự). Chú ý: Cần phân biệt hai hằng 'a' và "a". 'a' là hằng ký tự được lưu trữ trong 1 byte, còn "a" là hằng xâu ký tự được lưu trữ trong 1 mảng hai phần tử: phần tử thứ nhất chứa chữ a còn phần tử thứ hai chứa \0. Các hình thức chuyển đổi kiểu giá trị. Các toán tử chuyển đổi kiểu cho phép bạn chuyển đổi dữ liệu từ kiểu này sang kiểu khác. Có vài cách để làm việc này trong C++, cách cơ bản nhất được thừa kế từ 26
  28. ngôn ngữ C++ là đặt trước biểu thức cần chuyển đổi tên kiểu dữ liệu được bọc trong cặp ngoặc đơn (). Ví dụ 2.8 Cách 1: int i; float f = 3.14; i = (int) f; Cách 2: i = int (f); Cả hai cách chuyển đổi kiểu đều hợp lệ trong C++. Thêm vào đó ANSI-C++ còn có những toán tử chuyển đổi kiểu mới đặc trưng cho lập trình hướng đối tượng. 2.2.5. Biến a) Khai báo Để có thể sử dụng một biến trong C++, đầu tiên chúng ta phải khai báo nó, ghi rõ nó là kiểu dữ liệu nào. Chúng ta chỉ cần viết tên kiểu (như int, short, float, ) tiếp theo sau đó là một tên biến hợp lệ. Ví dụ 2.9 int a; float mynumber; Dòng đầu tiên khai báo một biến kiểu int với tên là a. Dòng thứ hai khai báo một biến kiểu float với tên mynumber. Sau khi được khai báo, các biến trên có thể được dùng trong phạm vi của chúng trong chương trình. Nếu bạn muốn khai báo một vài biến có cùng một kiểu và bạn muốn tiết kiệm công sức viết bạn có thể khai báo chúng trên một dòng, ngăn cách các tên bằng dấu phẩy. Ví dụ 2.10 int a, b, c; khai báo ba biến kiểu int (a,b và c) và hoàn toàn tương đương với: int a; int b; int c; 27
  29. Các kiểu số nguyên (char, short, long and int) có thể là số có dấu hay không dấu tuỳ theo miền giá trị mà chúng ta cần biểu diễn. Vì vậy khi xác định một kiểu số nguyên chúng ta đặt từ khoá signed hoặc unsigned trước tên kiểu dữ liệu. Ví dụ: unsigned short NumberOfSons; signed int MyAccountBalance; Nếu ta không chỉ rõ signed or unsigned nó sẽ được coi là có dấu, vì vậy trong khai báo thứ hai chúng ta có thể viết: int MyAccountBalance cũng hoàn toàn tương đương với dòng khai báo ở trên. Trong thực tế, rất ít khi người ta dùng đến từ khoá signed. Ngoại lệ duy nhất của luật này kiểu char. Trong chuẩn ANSI-C++ nó là kiểu dữ liệu khác với signed char và unsigned char. Để có thể thấy rõ hơn việc khai báo trong chương trình, chúng ta sẽ xem xét một đoạn mã C++ ví dụ như sau: b) Khởi tạo các biến Khi khai báo một biến, giá trị của nó mặc nhiên là không xác định. Nhưng có thể bạn sẽ muốn nó mang một giá trị xác định khi được khai báo. Để làm điều đó, bạn chỉ cần viết dấu bằng và giá trị bạn muốn biến đó sẽ mang: type = ; Ví dụ, nếu chúng ta muốn khai báo một biến int là a chứa giá trị 0 ngay từ khi khởi tạo, chúng ta sẽ viết: int a = 0; Bổ xung vào cách khởi tạo kiểu C này, C++ còn có thêm một cách mới để khởi tạo biến bằng cách bọc một cặp ngoặc đơn sau giá trị khởi tạo. Ví dụ: int a (0); Cả hai cách đều hợp lệ trong C++. c) Phạm vi hoạt động của các biến Tất cả các biến mà chúng ta sẽ sử dụng đều phải được khai báo trước. Một điểm khác biết giữa C và C++ là trong C++ chúng ta có thể khai báo biến ở bất kì nơi nào trong chương trình, thậm chí là ngay ở giữa các lệnh thực hiện chứ không chỉ là ở đầu khối lệnh như ở trong C. Biến toàn cục: có thể được sử dụng ở bất kì đâu trong chương trình, ngay sau khi nó được khai báo. 28
  30. Biến cục bộ: Tầm hoạt động của biến cục bộ bị giới hạn trong phần mã mà nó được khai báo. Nếu chúng được khai báo ở đầu một hàm (như hàm main), tầm hoạt động sẽ là toàn bộ hàm main. Điều đó có nghĩa là trong ví dụ trên, các biến được khai báo trong hàm main() chỉ có thể được dùng trong hàm đó, không được dùng ở bất kì đâu khác. Trong C++ tầm hoạt động của một biến chính là khối lệnh mà nó được khai báo (một khối lệnh là một tập hợp các lệnh được gộp lại trong một bằng các ngoặc nhọn { }). Nếu nó được khai báo trong một hàm tầm hoạt động sẽ là hàm đó, còn nếu được khai báo trong vòng lặp thì tầm hoạt động sẽ chỉ là vòng lặp đó, 2.2.6. Biến tham chiếu (reference) a) Sự tham chiếu Khi một con trỏ được khai báo, thì chưa có một địa chỉ nào gán cho nó cả. Địa chỉ tương quan với một con trỏ có thể được thay đổi (hay hình thành) qua phép gán. Trong thí dụ sau, biến ptr sẽ cài cho nó chỉ tới cùng một dữ liệu như là biến nguyên cơ bản a: int *ptr; int a; ptr = &a; Để làm được việc này, toán tử tham chiếu (hay còn gọi là tham chiếu ngược) & đã được dùng tới. Nó trả về vị trí của biến trong bộ nhớ (tức là địa chỉ) hay là chỗ chứa thực thể theo sau (trong thí dụ thì thực thể đó là a). Toán tử này như là công việc nó làm thường đuợc gọi là toán tử "địa chỉ". Trong trường hợp tổng quát, cụm từ giá trị tham chiếu được dùng để nói về địa chỉ trong bộ nhớ của sự tham chiếu (hay tham chiếu ngược). b) Sự tham chiếu ngược Cùng một biểu hiện, giá trị có thể đọc về từ một giá trị tham chiếu. Trong thí dụ sau, biến nguyên cơ bản b sẽ được gán đến dữ liệu mà dữ liệu đó được tham chiếu bởi ptr: int *ptr; int a, b; //Gán cho a giá trị là 10 a = 10; //Phép gán địa chỉ của a (tức là &a) lên con trỏ 'ptr' //để ptr bây giờ chỉ đến địa chỉ có nội dung là 10 ptr = &a; //Phép gán này cho b một giá trị nằm ở địa chỉ mà 'ptr' 29
  31. //Chỉ tới tức là giá trị của b sẽ là 10 b = *ptr Để hoàn tất được thao tác này, toán tử tham chiếu ngược (&) đã được dùng. Nó trả về dữ liệu cơ bản. Dữ liệu này có giá trị là một tham chiếu chỉ tới (tức là một địa chỉ). Biểu thức *ptr là một cách viết khác của giá trị 10 (gán cho b). Việc quá tải của kí tự * có hai biểu hiện liên hệ mà có thể gây ra sự nhầm lẫn. Hiểu được sự khác nhau giữa việc dùng nó như là một tiền tố trong một khai báo (con trỏ) và việc xem nó là toán tử tham chiếu trong một biểu thức là rất quan trọng. c) Sự tham chiếu tương đương và các mệnh đề cơ bản Bảng sau đây là danh sách các mệnh đề tương đương giữa kiểu cơ bản và kiểu tham chiếu (hay tham chiếu ngược). Trong đó, biến cơ bản d và biến tham chiếu ptr được hiểu ngầm. Các mệnh đề cơ bản và tham chiếu tương đương Đến một giá trị cơ bản Đến một giá trị tham chiếu Từ một giá trị cơ bản d &d Từ một giá trị tham chiếu *ptr ptr 2.2.7. Biến con trỏ (pointer) Con trỏ đối tượng dùng để chứa địa chỉ của biến đối tượng, được khai báo như sau: Kiểu_dữ_liệu * Tên_con_ trỏ; Ví dụ 2.11 int *p1, *p2, *p3; //Khai báo 3 con trỏ p1, p2, p3 int d1, d2; //Khai báo hai đối tượng d1, d2 int d[20]; //Khai báo mảng đối tượng Có thể thực hiện câu lệnh: p1 = &d2; //p1 chứa địa chỉ của d2, p1 trỏ tới d2 p2 =d; //p2 trỏ tới đầu mảng d 2.2.8. Chuyển đổi kiểu dữ liệu Khi tính toán một biểu thức phần lớn các phép toán đều yêu cầu các toán hạng phải cùng kiểu. Ví dụ để phép gán thực hiện được thì giá trị của biểu thức phải có cùng kiểu với biến. Trong trường hợp kiểu của giá trị biểu thức khác với kiểu của phép gán thì hoặc là chương trình sẽ tự động chuyển kiểu giá trị biểu thức về thành kiểu của biến được gán (nếu được) hoặc sẽ báo lỗi. Do vậy khi cần thiết người lập 30
  32. trình phải sử dụng các câu lệnh để chuyển kiểu của biểu thức cho phù hợp với kiểu của biến. a) Chuyển kiểu tự động: về mặt nguyên tắc, khi cần thiết các kiểu có giá trị thấp sẽ được chương trình tự động chuyển lên kiểu cao hơn cho phù hợp với phép toán. Cụ thể phép chuyển kiểu có thể được thực hiện theo sơ đồ như sau: char -> int -> long ->float -> double Ví dụ 2.12 int i = 3; float f; f = i + 2; Trong ví dụ trên i có kiểu nguyên và vì vậy i+2 cũng có kiểu nguyên trong khi f có kiểu thực. Tuy vậy phép toán gán này là hợp lệ vì chương trình sẽ tự động chuyển kiểu của i+2 (bằng 5) sang kiểu thực (bằng 5.0) rồi mới gán cho f. b) Ép kiểu: trong chuyển kiểu tự động, chương trình chuyển các kiểu từ thấp đến cao, tuy nhiên chiều ngược lại thì có thể gây mất dữ liệu. Do đó nếu cần thiết người lập trình phải ra lệnh cho chương trình. Cú pháp tổng quát như sau: (tên_kiểu)biểu_thức //Cú pháp cũ trong C hoặc: tên_kiểu(biểu_thức) //Cú pháp mới trong C++ Phép ép kiểu từ một số thực về số nguyên sẽ cắt bỏ tất cả phần thập phân của số thực, chỉ để lại phần nguyên. Như vậy để tính phần nguyên của một số thực x ta chỉ cần ép kiểu của x về thành kiểu nguyên, có nghĩa int(x) là phần nguyên của số thực x bất kỳ. Ví dụ để kiểm tra một số nguyên n có phải là số chính phương, ta cần tính căn bậc hai của n. Nếu căn bậc hai x của n là số nguyên thì n là số chính phương, tức nếu int(x) = x thì x nguyên và n là chính phương, ví dụ: int n = 10; float x = sqrt(n); //Hàm sqrt(n) tính căn bậc hai của n if (int(x) == x) cout > c; 31
  33. cout<<"Mã của kí tự vừa nhập là " << int(c); Xét ví dụ sau: int i = 3, j = 5; float x; x = i / j * 10; //x = 6? cout<<x; Trong ví dụ này mặc dù x được khai báo là thực nhưng kết quả in ra sẽ là 0 thay vì 6 như mong muốn. Lý do là vì phép chia giữa 2 số nguyên i và j sẽ cho lại số nguyên, tức i/j = 3/5 = 0. Từ đó x = 0*10 = 0. Để phép chia ra kết quả thực ta cần phải ép kiểu hoặc i hoặc j hoặc cả 2 thành số thực, khi đó phép chia sẽ cho kết quả thực và x được tính đúng giá trị. Cụ thể câu lệnh x = i/j*10 được đổi thành: x = float(i) / j * 10; //đúng x = i / float(j) * 10; //đúng x = float(i) / float(j) * 10; //đúng x = float(i/j) * 10; //sai Phép ép kiểu: x = float(i/j) * 10; vẫn cho kết quả sai vì trong dấu ngoặc phép chia i/j vẫn là phép chia nguyên, kết quả x vẫn là 0. 2.3. Biểu thức, câu lệnh và các phép toán 2.3.1. Biểu thức Biểu thức là một sự kết hợp giữa các phép toán và các toán hạng để diễn đạt một công thức toán học nào đó. Mỗi biểu thức có sẽ có một giá trị. Như vậy hằng, biến, phần tử mảng và hàm cũng được xem là biểu thức. Trong C++, ta có hai khái niệm về biểu thức : - Biểu thức gán - Biểu thức điều kiện Biểu thức được phân loại theo kiểu giá trị : nguyên và thực. Trong các mệnh đề logic, biểu thức được phân thành đúng (giá trị khác 0) và sai (giá trị bằng 0). Biểu thức thường được dùng trong: - Vế phải của câu lệnh gán. - Làm tham số thực sự của hàm. - Làm chỉ số. - Trong các toán tử của các cấu trúc điều khiển. Ta đã có hai khái niệm chính tạo nên biểu thức đó là toán hạng và phép toán. Toán hạng gồm : hằng, biến, phần tử mảng và hàm. 32
  34. Lệnh gán: Biểu thức gán là biểu thức có dạng : v=e; Trong đó v là một biến (hay phần tử mảng), e là một biểu thức. Giá trị của biểu thức gán là giá trị của e, kiểu của nó là kiểu của v. Nếu đặt dấu ; vào sau biểu thức gán ta sẽ thu được phép toán gán có dạng: v=e; Biểu thức gán có thể sử dụng trong các phép toán và các câu lệnh như các biểu thức khác. Ví dụ như khi ta viết a=b=5; thì điều đó có nghĩa là gán giá trị của biểu thức b=5 cho biến a. Kết qủa là b=5 và a=5. Hoàn toàn tương tự như: a=b=c=d=6; gán 6 cho cả a, b, c và d Ví dụ: z=(y=2)*(x=6); // ở đây * là phép toán nhân Gán 2 cho y, 6 cho x và nhân hai biểu thức lại cho ta z=12. Lệnh gán mở rộng: Một đặc tính của ngôn ngữ C++ làm cho nó nổi tiếng là một ngôn ngữ súc tích chính là các toán tử gán phức hợp cho phép chỉnh sửa giá trị của một biến với một trong những toán tử cơ bản sau: value += increase; tương đương với value = value + increase; a -= 5; tương đương với a = a - 5; a /= b; tương đương với a = a / b; price *= units + 1; tương đương với price = price * (units + 1); và tương tự cho tất cả các toán tử khác. 2.3.2. Các phép toán C++ có rất nhiều phép toán loại 1 ngôi, 2 ngôi và thậm chí cả 3 ngôi. Các thành phần tên gọi tham gia trong phép toán được gọi là hạng thức hoặc toán hạng, các kí hiệu phép toán được gọi là toán tử. Ví dụ trong phép toán a + b; a, b được gọi là toán hạng và + là toán tử. Phép toán 1 ngôi là phép toán chỉ có một toán hạng, ví dụ -a (đổi dấu số a), &x (lấy địa chỉ của biến x), Một số kí hiệu phép toán cũng được sử dụng chung cho cả 1 ngôi lẫn 2 ngôi (hiển nhiên với ngữ nghĩa khác nhau). 33
  35. a) Các phép toán số học: +, -, *, /, % Các phép toán + (cộng), (trừ), * (nhân) được hiểu theo nghĩa thông thường trong số học. Phép toán a / b (chia) được thực hiện theo kiểu của các toán hạng, tức nếu cả hai toán hạng là số nguyên thì kết quả của phép chia chỉ lấy phần nguyên, ngược lại nếu 1 trong 2 toán hạng là thực thì kết quả là số thực. Ví dụ: Kết quả bằng 2 do 13 và 5 là 2 số nguyên: 13/5 = 2 Kết quả bằng 2.6 do có ít nhất 1 toán hạng là thực: 13.0/5 = 13/5.0 = 13.0/5.0 = 2.6 Phép toán a % b (lấy phần dư) trả lại phần dư của phép chia a/b, trong đó a và b là 2 số nguyên. Ví dụ: 13%5 = 3 //Phần dư của 13/5 5%13 = 5 //Phần dư của 5/13 b) Các phép toán tự tăng, giảm: i++, ++i, i , i Phép toán ++i và i++ sẽ cùng tăng i lên 1 đơn vị tức tương đương với câu lệnh i = i+1. Tuy nhiên nếu 2 phép toán này nằm trong câu lệnh hoặc biểu thức thì ++i khác với i++. Cụ thể ++i sẽ tăng i, sau đó i mới được tham gia vào tính toán trong biểu thức. Ngược lại i++ sẽ tăng i sau khi biểu thức được tính toán xong (với giá trị i cũ). Điểm khác biệt này được minh hoạ thông qua ví dụ sau, giả sử i = 3, j = 15. Phép toán Tương đương Kết quả i = ++j; = j + 1; i = j; i= 16, j = 16 i= j++; i = j; j = j + 1; i=15, j = 16 j = ++i + 5; i = i + 1; j = i + 5; i=4, j = 9 j = i++ + 5; j = i + 5; i = i + 1; i=4, j = 8 c) Các phép toán so sánh và lôgic Đây là các phép toán mà giá trị trả lại là đúng hoặc sai. Nếu giá trị của biểu thức là đúng thì nó nhận giá trị 1, ngược lại là sai thì biểu thức nhận giá trị 0. Nói cách khác 1 và 0 là giá trị cụ thể của 2 khái niệm "đúng", "sai". Mở rộng hơn C++ quan niệm một giá trị bất kỳ khác 0 là "đúng" và giá trị 0 là "sai". Các phép toán so sánh: 34
  36. == (bằng nhau), != (khác nhau), > (lớn hơn), = (lớn hơn hoặc bằng), = 2) //= 4 vì 5>=2 bằng 1 Chú ý: cần phân biệt phép toán gán (=) và phép toán so sánh (==). Phép gán vừa gán giá trị cho biến vừa trả lại giá trị bất kỳ (là giá trị của toán hạng bên phải), trong khi phép so sánh luôn luôn trả lại giá trị 1 hoặc 0. Các phép toán lôgic: && (và), || (hoặc ), ! (không, phủ định) Hai toán hạng của loại phép toán này phải có kiểu lôgic tức chỉ nhận một trong hai giá trị "đúng" (được thể hiện bởi các số nguyên khác 0) hoặc "sai" (thể hiện bởi 0). Khi đó giá trị trả lại của phép toán là 1 hoặc 0 và được cho trong bảng sau: a b a && b a || b ! a 1 1 1 1 0 1 0 0 1 0 0 1 0 1 1 0 0 0 0 1 Tóm lại: Phép toán "và" đúng khi và chỉ khi hai toán hạng cùng đúng Phép toán "hoặc" sai khi và chỉ khi hai toán hạng cùng sai Phép toán "không" (hoặc "phủ định") đúng khi và chỉ khi toán hạng của nó sai. Ví dụ 2.13 7 || 0 //có giá trị bằng 1 3 6 //có giá trị bằng 1 4 && 6 //có giá trị bằng 1 d) Các phép tính theo bit Toán tử bit coi các toán hạng của nó như một tập hợp các bit. Mỗi bit có thể chứa hoặc giá trị 0 (tắt) hay 1 (bật). Toán tử bit cho phép người lập trình kiểm thử và đặt giá trị cho từng bit hay nhóm bit. Toán hạng của toán tử bit phải có kiểu nguyên. Việc dùng toán hạng không dấu làm cho chương trình phù hợp hơn với mọi cài đặt. 35
  37. Bảng sau nói về các phép toán bit: Toán tử Chức năng Cách dùng ~ Phủ định NOT ~bt > dịch chuyển phải bt1>>bt2 & Và bit AND bt1&bt2 ^ hoặc bit XOR bt1^bt2 | hoặc bit OR bt1|bt2 Toán tử bit NOT lật lại giá trị từng bit của toán hạng. Mỗi bit 1 đc đặt thành 0 và ngược lại. Ví dụ 2.14 Các phép toán dịch chuyển bit sẽ chuyển các bit của toán hạng bên trái đi một số vị trí hoặc sang trái hoặc phải. Ví dụ 2.15 Ví dụ 2.16 Toán tử AND bit nhận hai toán hạng, với mỗi vị trí bit, kết quả cho lại là bit 1 nếu cả hai toán hạng đều chứa bit 1, ngược lại kết quả là bit 0 (các bạn chú ý không nhầm toán tử này với toán tử AND logic (&&)). Ví dụ 2.17 36
  38. Toán tử XOR bit nhân hai toán hạng, với mỗi vị trí bit, kết quả cho lại là bit 1 nếu một trong hai nhưng không đồng thời cả hai toán hạng có chứa bit 1, ngược lại kết quả là bit 0. Ví dụ 2.18 Toán tử OR bit nhận hai toán hạng, với mỗi vị trí bit, kết quả cho lại là bit 1 nếu một trong hai toán hạng có chứa bit 1, ngược lại kết quả là bit 0. Ví dụ 2.19 e) Toán tử sizeof Toán tử này có một tham số, đó có thể là một kiểu dữ liệu hay là một biến và trả về kích cỡ bằng byte của kiểu hay đối tượng đó. a = sizeof (char); a sẽ mang giá trị 1 vì kiểu char luôn có kích cỡ 1 byte trên mọi hệ thống. Giá trị trả về của sizeof là một hằng số vì vậy nó luôn luôn được tính trước khi chương trình thực hiện. f) Toán tử điều kiện. Toán tử điều kiện tính toán một biểu thức và trả về một giá trị khác tuỳ thuộc vào biểu thức đó là đúng hay sai. Cú pháp: ? : 37
  39. Nếu condition là true thì giá trị trả về sẽ là result1, nếu không giá trị trả về là result2. 7==5?4: 3 //Trả về 3 vì 7 không bằng 5. 7==5+2?4: 3 //Trả về 4 vì 7 bằng 5+2. 5>3?a: b //Trả về a, vì 5 lớn hơn 3. a>b?a: b //Trả về giá trị lớn hơn, a hoặc b. g) Toán tử dãy Mỗi câu lệnh C++ được kết thúc bằng dấu. Tuy nhiên khái niệm một biểu thức trong C++ được mở rộng hơn, nó được kết thúc bằng dấu chấm phẩy song nó có thể chứa một dãy các lệnh, dãy biểu thức tính toán độc lập chứ không phải chỉ có một biểu thức tính một giá trị. Ví dụ 2.20 a*b, i+j; Là một biểu thức tính a*b như là một giá trị, sau đó tính một giá trị khác là i+j. Hai tính toán này được đặt cách nhau bằng một dấu phẩy chứ không phải là dấu chấm phẩy. 2.3.3. Thứ tự ưu tiên các phép toán Các phép toán có độ ưu tiên khác nhau, điều này có ý nghĩa trong cùng một biểu thức sẽ có một số phép toán này được thực hiện trước một số phép toán khác. Thứ tự ưu tiên của các phép toán được trình bày trong bảng sau: Thứ tự Phép toán Trình tự kết hợp 1 :: Trái qua phải 2 () [] . -> ++ dynamic_cast static_cast Trái qua phải reinterpret_cast const_cast typeid 3 ++ ~ ! sizeof new delete Phải qua trái * & + - 4 (type) Phải qua trái 5 .* ->* Trái qua phải 6 * / % Trái qua phải 7 + - Trái qua phải 8 > Trái qua phải 9 = Trái qua phải 10 == != Trái qua phải 11 & Trái qua phải 38
  40. 12 ^ Trái qua phải 13 | Trái qua phải 14 && Trái qua phải 15 || Trái qua phải 16 ?: Phải qua trái 17 = *= /= %= += -= >>= > x >> y; x = 3 + x; y = (x = sqrt(x)) + 1; cout<<x; cout<<y; Lệnh hợp thành hay lệnh ghép (Compound statement) Một dãy các câu lệnh được bao bởi các dấu { } gọi là một khối lệnh. Ví dụ: { a=2; b=3; printf("\n%6d%6d",a,b); } C++ xem khối lệnh cũng như một câu lệnh riêng lẻ. Nói cách khác, chỗ nào viết được một câu lệnh thì ở đó cũng có quyền đặt một khối lệnh. Sự lồng nhau của các khối lệnh và phạm vi hoạt động của các biến: Bên trong một khối lệnh lại có thể viết lồng khối lệnh khác. Sự lồng nhau theo cách như vậy là không hạn chế. Khi máy bắt đầu làm việc với một khối lệnh thì các biến và mảng khai báo bên trong nó mới được hình thành và được cấp phát bộ nhớ. Các biến này chỉ tồn tại 39
  41. trong thời gian máy làm việc bên trong khối lệnh và chúng lập tức biến mất ngay sau khi máy ra khỏi khối lệnh. Vậy giá trị của một biến hay một mảng khai báo bên trong một khối lệnh không thể đưa ra sử dụng ở bất kỳ chỗ nào bên ngoài khối lệnh đó. Ở bất kỳ chỗ nào bên ngoài một khối lệnh ta không thể can thiệp đến các biến và các mảng được khai báo bên trong khối lệnh Nếu bên trong một khối ta dùng một biến hay một mảng có tên là a thì điều này không làm thay đổi giá trị của một biến khác cũng có tên là a (nếu có) được dùng ở đâu đó bên ngoài khối lệnh này. Nếu có một biến đã được khai báo ở ngoài một khối lệnh và không trùng tên với các biến khai báo bên trong khối lệnh này thì biến đó cũng có thể sử dụng cả bên trong cũng như bên ngoài khối lệnh. Ví dụ 2.21 Xét đoạn chương trình sau: { int a=5,b=2; { int a=4; b=a+b; cout<<a << endl << b << endl; } cout<<a << endl << b << endl; } Khi đó đoạn chương trình sẽ in kết quả như sau: a trong =4 b=6 a ngoài =5 b=6 Do tính chất biến a trong và ngoài khối lệnh. 2.3.5. Một số hàm số học Tóm tắt một số các hàm toán học hay dùng. Các hàm này đều được khai báo trong file nguyên mẫu math.h. - abs(x), labs(x), fabs(x): trả lại giá trị tuyệt đối của một số nguyên, số nguyên dài và số thực. - pow(x, y): hàm mũ, trả lại giá trị x lũy thừa y (xy). - exp(x): hàm mũ, trả lại giá trị e mũ x (ex). 40
  42. - log(x), log10(x): trả lại lôgarit cơ số e và lôgarit thập phân của x (lnx, logx). - sqrt(x): trả lại căn bậc 2 của x. - atof(s_number): trả lại số thực ứng với số viết dưới dạng xâu kí tự s_number. - sin(x), cos(x), tan(x): trả lại các giá trị sinx, cosx, tgx. 41
  43. B. Phần thảo luận, bài tập Bài 1. Nêu thứ tự thực hiện các phép toán trong biểu thức ở câu lệnh cout và cho biết kết quả in ra màn hình sau khi thực hiện chương trình sau: #include void main(){ cout void main(){ cout void main(){ int a=2,b=2; cout >2&7); } Bài 4. Nêu tác dụng của từng câu lệnh trong hàm main và cho biết kết quả in ra màn hình sau khi thực hiện chương trình sau: #include void main(){ char *s="abcdefgh",*st=s; st+=4; *st+=4; s+=1; *s+=1; cout void main(){ unsigned char c=200; float f=4.5; c+=100; f+=0.5; cout<<f/2+c/3; } 42
  44. CHƯƠNG 3. CÁC THAO TÁC XỬ LÝ INPUT/OUTPUT A. Phần lý thuyết 3.1. Hàm in ra màn hình printf() và putchar() với các tham số 3.1.1. Hàm printf Để in các giá trị bt_1, bt_2, , bt_n ra màn hình theo một khuôn dạng mong muốn ta có thể sử dụng cú pháp sau đây: printf(Chuỗi định dạng, bt_1, bt_2, , bt_n) ; trong đó Chuỗi định dạng là một dãy kí tự đặt trong cặp dấu nháy kép (“”) qui định khuôn dạng cần in của các giá trị bt_1, bt_2, , bt_n. Các bt_i có thể là các hằng, biến hay các biểu thức tính toán. Câu lệnh trên sẽ in giá trị của các bt_i này theo thứ tự xuất hiện của chúng và theo qui định được cho trong dòng định dạng. Ví dụ, giả sử x = 4, câu lệnh: printf(“%d %0.2f”, 3, x + 1) ; sẽ in các số 3 và 5.00 ra màn hình, trong đó 3 được in dưới dạng số nguyên (được qui định bởi “%d”) và x + 1 (có giá trị là 5) được in dưới dạng số thực với 2 số lẻ thập phân (được qui định bởi “%0.2f”). Các kí tự đi sau kí hiệu % dùng để định dạng việc in gồm có: - d in số nguyên dưới dạng hệ thập phân - o in số nguyên dạng hệ 8 - x, X in số nguyên dạng hệ 16 - u in số nguyên dạng không dấu - c in kí tự - s in xâu kí tự - e, E in số thực dạng dấu phẩy động - f in số thực dạng dấu phẩy tĩnh Các kí tự trên phải đi sau dấu %. Các kí tự nằm trong dòng định dạng nếu không đi sau % thì sẽ được in ra màn hình. Muốn in % phải viết 2 lần (tức %%). 43
  45. Ví dụ câu lệnh: printf(“Tỉ lệ học sinh giỏi: %0.2f %%”, 32.486) ; sẽ in câu “Tỉ lệ học sinh giỏi: “, tiếp theo sẽ in số 32.486 được làm tròn đến 2 số lẻ thập phân lấp vào vị trí của “%0.2f”, và cuối cùng sẽ in dấu “%” (do có %% trong dòng định dạng). Câu được in ra màn hình sẽ là: Tỉ lệ học sinh giỏi: 32.49% Chú ý: Mỗi bt_i cần in phải có một định dạng tương ứng trong dòng định dạng. Ví dụ câu lệnh trên cũng có thể viết: printf(“%s %0.2f”, “Tỉ lệ học sinh giỏi: “, 32.486); Trong câu lệnh này có 2 biểu thức cần in. Biểu thức thứ nhất là xâu kí tự “Tỉ lệ học sinh giỏi:” được in với khuôn dạng %s (in xâu kí tự) và biểu thức thứ hai là 32.486 được in với khuôn dạng %0.2f (in số thực với 2 số lẻ phần thập phân). Nếu giữa kí tự % và kí tự định dạng có số biểu thị độ rộng cần in thì giá trị in ra sẽ được gióng cột sang lề phải, để trống các dấu cách phía trước. Nếu độ rộng âm (thêm dấu trừ − phía trước) sẽ gióng cột sang lề trái. Nếu không có độ rộng hoặc độ rộng bằng 0 (ví dụ %0.2f) thì độ rộng được tự điều chỉnh đúng bằng độ rộng của giá trị cần in. Dấu + trước độ rộng để in giá trị số kèm theo dấu (dương hoặc âm) Trước các định dạng số cần thêm kí tự l (ví dụ ld, lf) khi in số nguyên dài long hoặc số thực với độ chính xác gấp đôi double. Các ký tự điều khiển: - \n sang dòng mới - \b lùi lại 1 tab. - \f sang trang mới - \t dấu tab - \' In ra dấu ' - \" In ra dấu " - \\: In ra dấu \ Ví dụ 3.1 #include void main() { int i = 2, j = 3 ; 44
  46. printf(“Tổng 2 số nguyên:\ni + j = %d”, i+j); } 3.1.2. Hàm putchar() Để đưa một ký tự ra màn hình, sử dụng hàm putchar() với cú pháp như sau: putchar(ch); Hàm này sẽ đưa ký tự ch lên màn hình tại vị trí hiện tại của con trỏ. Hàm putchar() nằm trong thư viện stdio.h. Ví dụ: putchar('A'); > in ra ký tự A. 3.2. Hàm đọc ký tự từ bàn phím Hàm getch(): nhận 1 ký tự trực tiệp từ bộ đệm bàn phím và trả về ký tự nhận được. Hàm getch() nằm trong thư viện conio.h. Hàm getchc(): nhận 1 ký tự trực tiếp từ bộ đệm bàn phím và hiển thị trên monitor. Hàm getch() nằm trong thư viện stdio.h. 3.3. Thực hiện Input/Output Để xuất dữ liệu ra màn hình và nhập dữ liệu từ bàn phím, trong C++ vẫn có thể dùng hàm printf() và scanf(), ngoài ra trong C++ ta có thể dùng dòng xuất/nhập chuẩn để nhập/xuất dữ liệu thông qua hai biến đối tượng của dòng (stream object) là cout và cin. 3.3.1. Nhập dữ liệu a) Toán tử >> Toán tử >> được sử dụng như sau để đọc dữ liệu từ dòng cin. Cú pháp: cin>>biến 1>>biến 2>> >>biến n; Toán tử cin được định nghĩa trước như một đối tượng biểu diễn cho thiết bị vào chuẩn của C++ là bàn phím, cin được sử dụng kết hợp với toán tử trích >> để nhập dữ liệu từ bàn phím cho các biến 1, 2, , N. b) Nhập ký tự và chuỗi ký tự Có thể dùng các phương thức sau (định nghĩa trong lớp istream) để nhập ký tự và chuỗi: cin.getcin.getline cin.ignore  Phương thức get(): 45
  47. Dạng 1: int cin.get(); Dùng để đọc một ký tự (kể cả khoảng trắng). Ví dụ 3.2 #include void main() { int a; //Khai báo biến a kiểu int a=cin.get();//Đọc giá từ bàn phím trả về mã ACSII của ký tự cout void main() { char b[10]; cin.get(b,10); cout còn lại trên dòng nhập có thể làm trôi phương thức get() dạng 3. Ví dụ xét đoạn chương trình: Ví dụ 3.4 46
  48. #include void main() { char hoten[25], diachi[50], quequan[30]; cout thì câu lệnh get đầu tiên sẽ nhận được chuỗi “Nguyen van X” cất vào mảng hoten. Ký tự còn lại sẽ làm trôi 2 câu lệnh get tiếp theo. Do đó câu lệnh cuối cùng sẽ chỉ in ra Nguyen van X. Để khắc phục tình trạng trên, có thể dùng một trong các cách sau: - Dùng phương thức get() dạng 1 hoặc dạng 2 để lấy ra ký tự trên dòng nhập trước khi dùng get (dạng 3). - Dùng phương thức ignore để lấy ra một số ký tự không cần thiết trên dòng nhập trước khi dùng get dạng 3. Phương thức này viết như sau:  cin.ignore(n); Lấy ra (loại ra hay loại bỏ) n ký tự trên dòng nhập. Như vậy để có thể nhập được cả quê quán và cơ quan, cần sửa lại đoạn chương trình trên như sau: Ví dụ 3.5 #include void main() { char hoten[25], diachi[50], quequan[30]; cout<<"\nHo ten: "; cin.get(hoten,25); cin.ignore(1); cout<<"\nDia chi: "; cin.get(diachi,50); cin.ignore(1); cout<<"\nQue quan: "; cin.get(quequan,30); cin.ignore(1); cout<<endl<< hoten<< " " << diachi << " " << quequan; } 47
  49.  Phương thức getline() Phương thức getline để nhập một dãy ký tự từ bàn phím. Cú pháp: istream& cin.getline(char *str, int n, char d =‘\n’); Ví dụ 3.6 #include using namespace std; void main () { char name[256], title[256]; cout > sẽ để lại ký tự chuyển dòng ’\n’ trong bộ đệm. Ký tự này có thể làm trôi phương thức cin.get. Để khắc phục tình trạng trên cần dùng phương thức cin.ignore(1) để bỏ qua một ký tự chuyển dòng. - Để sử dụng các loại toán tử và phương thức nói trên cần khai báo tập tin dẫn hướng iostream.h 3.3.2. Xuất dữ liệu Cú pháp: cout<<biểu thức 1 << << biểu thức N; Trong đó cout được định nghĩa trước như một đối tượng biểu diễn cho thiết bị xuất chuẩn của C++ là màn hình, cout được sử dụng kết hợp với toán tử chèn << để hiển thị giá trị các biểu thức 1, 2, , N ra màn hình. 3.4. Thiết lập khuôn dạng - Trình bày màn hình 3.4.1. Các phương thức định dạng Phương thức int cout.width(): Cho biết độ rộng quy định hiện tại. 48
  50. Phương thức int cout.width(int n): Thiết lập độ rộng quy định mới là n và trả về độ rộng quy định trước đó. Chú ý: Độ rộng quy định n chỉ có tác dụng cho một giá trị xuất. Sau đó C++ lại áp dụng độ rộng quy định bằng 0. Ví dụ với các câu lệnh: int m=1234, n=56; cout #include void main() { clrscr(); float x=-3.1551, y=-23.45421; cout.precision(2); cout.fill(‘*’); cout<<”\n”; cout.width(8); cout<<x; cout<<”\n”; cout.width(8); cout<<y; } 49
  51. Sau khi thực hiện, chương trình in ra màn hình 2 dòng sau: -3.16 -23.45 3.4.2. Cờ định dạng Mỗi cờ định dạng chứa trong một bit. Cờ có 2 trạng thái: Bật (on) – có giá trị 1, Tắt (off) – có giá trị 0. Các cờ có thể chứa trong một biến kiểu long. Trong tập tin iostream.h đã định nghĩa các cờ sau: ios::left ios::right ios::internal ios::dec ios::oct ios::hex ios::fixed ios::scientific ios::showpos ios::uppercase ios::showpoint ios::showbase Có thể chia cờ định dạng thành các nhóm:  Nhóm 1 gồm các cờ căn lề: ios::left ios::right ios::internal - Cờ ios::left: khi bật cờ ios::left thì giá trị in ra nằm bên trái vùng quy định, các ký tự độn nằm sau. - Cờ ios::right: khi bật cờ ios::right thì giá trị in ra nằm bên phải vùng quy định, các ký tự độn nằm trước. Chú ý mặc định cờ ios::right bật. - Cờ ios::internal: cờ ios::internal có tác dụng giống như cờ ios::right chỉ khác là dấu (nếu có) in đầu tiên. Chương trình sau minh hoạ cách dùng các cờ căn lề: Ví dụ 3.8 #include #include void main() { clrscr(); float x=-87.1551, y=23.45421; cout.precision(2); cout.fill('*'); cout.setf(ios::left); //bat co ios::left cout<<"\n"; cout.width(8); cout<<x; cout<<"\n"; 50
  52. cout.width(8); cout int main() { cout.setf(ios::hex, ios::basefield); cout<<100; }  Nhóm 3 gồm các cờ định dạng số thực: ios::fixed ios::scientific ios::showpoint Mặc định: cờ ios::fixed bật (on) và cờ ios::showpoint tắt (off). 51
  53. - Khi ios::fixed bật và cờ ios::showpoint tắt thì số thực in ra dưới dạng thập phân, số chữ số phần phân (sau dấu chấm) được tính bằng độ chính xác n nhưng khi in thì bỏ đi các chữ số 0 ở cuối. Ví dụ nếu độ chính xác n = 4 thì Số thực -87.1500 được in: -87.15 Số thực 23.45425 được in: 23.4543 Số thực 678.0 được in: 678 - Khi ios::fixed bật và cờ ios::showpoint bật thì số thực in ra dưới dạng thập phân, số chữ số phần phân (sau dấu chấm) được in ra đúng bằng độ chính xác n. Ví dụ nếu độ chính xác n = 4 thì Số thực -87.1500 được in: -87.1500 Số thực 23.45425 được in: 23.4543 Số thực 678.0 được in: 678.0000 - Khi ios::scientific bật và cờ ios::showpoint tắt thì số thực in ra dưới dạng khoa học. Số chữ số phần phân (sau dấu chấm) được tính bằng độ chính xác n nhưng khi in thì bỏ đi các chữ số 0 ở cuối. Ví dụ nếu độ chính xác n=4 thì Số thực -87.1500 được in: -87.15e+01 Số thực 23.45425 được in: 23.4543e+01 Số thực 678.0 được in: 678e+02 - Khi ios::scientific bật và cờ ios::showpoint bật thì số thực in ra dưới dạng mũ. Số chữ số phần phân (sau dấu chấm) của phần định trị được in đúng bằng độ chính xác n. Ví dụ nếu độ chính xác n=4 thì Số thực -87.1500 được in: -87.150e+01 Số thực 23.45425 được in: 23.4543e+01 Số thực 678.0 được in: 67800e+01 Chương trình sau minh hoạ cách dùng các cờ định dạng số thực: Ví dụ 3.10 #include 52
  54. #include int main() { cout.setf(ios::hex, ios::basefield); cout #include void main() { float a=5.3333333; cout<<setiosflags(ios:: showpoint) << setprecision(3); cout<<a; }  Nhóm 4 gồm các hiển thị: ios::uppercase ios::showpos ios::showbase - Cờ ios::showpos: 53
  55. + Nếu cờ ios::showpos tắt (mặc định) thì dấu cộng không được in trước số dương. + Nếu cờ ios::showpos tắt thì dấu cộng được in trước số dương. + Cờ ios::showbase bật thì số nguyên hệ 8 được in bắt đầu bằng ký tự 0 và số nguyên hệ 16 được bắt đầu bằng các ký tự 0x. Ví dụ nếu a = 40 thì: Dạng in hệ 8 là: 050 Dạng in hệ 16 là 0x28 - Cờ ios::showbase: tắt (mặc định) thì không in 0 trước số nguyên hệ 8 và không 0x trước số nguyên hệ 16. Ví dụ nếu a = 40 thì: Dạng in hệ 8 là: 50 Dạng in hệ 16 là 28 - Cờ ios::uppercase: + Nếu cờ ios::uppercase bật thì các chữ số hệ 16 (như A, B, C, ) được in dưới dạng chữ hoa. + Nếu cờ ios::uppercase tắt (mặc định) thì các chữ số hệ 16 (như A, B, C, ) được in dưới dạng chữ thường. Chương trình sau minh hoạ cách dùng các cờ định dạng số thực: Ví dụ 3.12 #include int main() { cout.setf(ios::showpoint|ios::uppercase|ios::scientific); cout<<100.0; } 3.4.3. Các phương thức bật tắt cờ Các phương thức này định nghĩa trong lớp ios. - Phương thức long cout.setf(long f): Sẽ bật các cờ liệt kê trong f và trả về một giá trị long biểu thị các cờ đang bật. - Phương thức long cout.unsetf(long f): Sẽ tắt các cờ liệt kê trong f và trả về một giá trị long biểu thị các cờ đang bật. 54
  56. - Phương thức long cout.flags(long f): Có tác dụng giống như cout.setf(long). - Phương thức long cout.flags(): Sẽ trả về một giá trị long biểu thị các cờ đang bật. 55
  57. B. Phần thảo luận, bài tập Bài 1. Viết chương trình đọc 2 số nguyên và in ra kết quả của phép (+), phép trừ (-), phép nhân (*), phép chia (/) ra màn hình. Nhận xét kết quả chia 2 số nguyên. Bài 2. Viết chương trình nhập vào bán kính r của một hình tròn. Tính chu vi và diện tích của hình tròn theo công thức: Chu vi CV = 2*Pi*r Diện tích S = Pi*r*r In các kết quả lên màn hình. Bài 3. Viết chương trình nhập vào bán kính hình cầu, tính và in ra diện tích, thể tích 2 3 của hình cầu đó. Hướng dẫn: S 4* PI * R , và V (4/3) * PI * R 2 Bài 4. Viết chương trình nhập vào một số a bất kỳ và in ra giá trị bình phương (a ), 3 4 lập phương (a ) của a và giá trị a . Bài 5. Viết chương trình nhập vào điểm ba môn Toán, Lý, Hóa của một học sinh. In ra điểm trung bình của học sinh đó với hai số lẻ thập phân. Bài 6. Viết chương trình đảo ngược một số nguyên dương có đúng 3 chữ số. 56
  58. CHƯƠNG 4. CÁC CẤU TRÚC ĐIỀU KHIỂN A. Phần lý thuyết Một chương trình thường không chỉ bao gồm các lệnh tuần tự nối tiếp nhau. Trong quá trình chạy nó có thể rẽ nhánh hay lặp lại một đoạn mã nào đó. Để làm điều này chúng ta sử dụng các cấu trúc điều khiển. Cùng với việc giới thiệu các cấu trúc điều khiển chúng ta cũng sẽ phải biết tới một khái niệm mới: khối lệnh, đó là một nhóm các lệnh được ngăn cách bởi dấu chấm phẩy (;) nhưng được gộp trong một khối giới hạn bởi một cặp ngoặc nhọn: { và }. Hầu hết các cấu trúc điều khiển mà chúng ta sẽ xem xét trong chương này cho phép sử dụng một lệnh đơn hay một khối lệnh làm tham số, tuỳ thuộc vào chúng ta có đặt nó trong cặp ngoặc nhọn hay không. 4.1. Cấu trúc if Cấu trúc này được dùng khi một lệnh hay một khối lệnh chỉ được thực hiện khi một điều kiện nào đó thoả mãn. Cú pháp: if (Điều kiện) ; Nếu đúng (biểu thức có giá trị khác 0) máy sẽ thực hiện và sau đó sẽ thực hiện các lệnh tiếp sau lệnh if trong chương trình. Nếu biểu thức sai (biểu thức có giá trị bằng 0) thì máy bỏ qua mà thực hiện ngay các lệnh tiếp sau lệnh if trong chương trình. Ví dụ, đoạn mã sau đây sẽ viết “x is 100” chỉ khi biến x chứa giá trị 100: if (x == 100) cout là true chúng ta có thể chỉ định một khối lệnh bằng cách sử dụng một cặp ngoặc nhọn {}: if (x == 100){ cout<<"x is "; cout<<x; } 57
  59. Chúng ta cũng có thể chỉ định điều gì sẽ xảy ra nếu điều kiện không được thoả mãn bằng cách sử dụng từ khoá else. Cú pháp: if (Biểu thức) ; else ; Máy tính giá trị của (Biểu thức). Nếu (Biểu thức) đúng (biểu thức có giá trị khác 0) máy sẽ thực hiện và sau đó sẽ thực hiện các lệnh tiếp sau trong chương trình. Nếu (Biểu thức) sai (biểu thức có giá trị bằng 0) thì máy bỏ qua mà thực hiện sau đó thực hiện tiếp các lệnh tiếp sau trong chương trình. Ví dụ 4.1: Viết chương trình nhập hai số nguyên a, b từ bàn phím. In lên mà hình số lớn nhất của hai số đó. #include #include #include void main() { int a,b,Max; cout >a; cout >b; Max=a; if (Max #include #include void main() { float a, b, x; cout >a; cout >b; if (a == 0.0) if (b == 0.0) cout<<"Phuong trinh vo dinh" ; 58
  60. else cout 0) cout ; case n2 ; case nk ; [ default ; ] } Với ni là các số nguyên, hằng ký tự hoặc biểu thức hằng. Các ni cần có giá trị khác nhau. Đoạn chương trình nằm giữa các dấu { } gọi là thân của toán tử switch. default là một thành phần không bắt buộc phải có trong thân của switch. 59
  61. Sự hoạt động của toán tử switch phụ thuộc vào giá trị của biểu thức viết trong dấu ngoặc ( ) như sau: Khi giá trị của biểu thức này bằng ni, máy sẽ nhảy tới các câu lệnh có nhãn là case ni. Khi giá trị biểu thức khác tất cả các ni thì cách làm việc của máy lại phụ thuộc vào sự có mặt hay không của lệnh default như sau: Khi có default máy sẽ nhảy tới câu lệnh sau nhãn default. Khi không có default máy sẽ nhảy ra khỏi cấu trúc switch. Chú ý Máy sẽ nhảy ra khỏi toán tử switch khi nó gặp câu lệnh break hoặc dấu ngoặc nhọn đóng cuối cùng của thân switch. Ta cũng có thể dùng câu lệnh goto trong thân của toán tử switch để nhảy tới một câu lệnh bất kỳ bên ngoài switch. Khi toán tử switch nằm trong thân một hàm nào đó thì ta có thể sử dụng câu lệnh return trong thân của switch để ra khỏi hàm này (lệnh return sẽ đề cập sau). Khi máy nhảy tới một câu lệnh nào đó thì sự hoạt động tiếp theo của nó sẽ phụ thuộc vào các câu lệnh đứng sau câu lệnh này. Như vậy nếu máy nhảy tới câu lệnh có nhãn case ni thì nó có thể thực hiện tất cả các câu lệnh sau đó cho tới khi nào gặp câu lệnh break, goto hoặc return. Nói cách khác, máy có thể đi từ nhóm lệnh thuộc case ni sang nhóm lệnh thuộc case thứ ni+1. Nếu mỗi nhóm lệnh được kết thúc bằng break thì toán tử switch sẽ thực hiện chỉ một trong các nhóm lệnh này. Hai đoạn mã sau là tương đương: switch if-else tương đương switch (x) { if (x == 1) { case 1: cout<<"x = 1"; cout<<"x = 1"; } break; else if (x == 2) { case 2: cout<<"x = 2"; cout<<"x = 2"; } break; else { default: cout<<"x=?"; cout<<"x=?"; } } Ví dụ 4.3: Viết chương trình nhập vào một tháng của năm (tính theo dương lịch), tính xem tháng đó có bao nhiêu ngày rồi in kết quả lên màn hình 60
  62. #include #include #include void main() { int t, n; cout >t; cout >n; switch (t) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: cout ; ; ) ; Và chức năng chính của nó là lặp lại chừng nào còn mang giá trị đúng, như trong vòng lặp while. Nhưng thêm vào đó, for cung cấp chỗ dành cho lệnh khởi tạo và lệnh tăng (giảm) . Vì vậy vòng lặp này được thiết kế đặc biệt lặp lại một hành động với một số lần xác định. Cách thức hoạt động của nó như sau: 1. được thực hiện. Nói chung nó đặt một giá trị ban đầu cho biến điều khiển. Lệnh này được thực hiện chỉ một lần. 61
  63. 2. được kiểm tra, nếu nó là đúng vòng lặp tiếp tục còn nếu không vòng lặp kết thúc và được bỏ qua. 3. được thực hiện. Nó có thể là một lệnh đơn hoặc là một khối lệnh được bao trong một cặp ngoặc nhọn. 4. Cuối cùng, được thực hiện để tăng biến điều khiển và vòng lặp quay trở lại bước 2. Ví dụ 4.4: Tính tổng: S 12 22 32 n 2 (n nguyên dương) #include #include #include void main() { int i, n; double s=0; cout >n; for (i=1;i void main() { for (int n=10; n>0; n ){ cout<<n << ", "; } } Kết quả: 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, Phần khởi tạo và lệnh tăng không bắt buộc phải có. Chúng có thể được bỏ qua nhưng vẫn phải có dấu chấm phẩy ngăn cách giữa các phần. Vì vậy, chúng ta có thể viết for (;n<10;) hoặc for (;n<10;n++). Bằng cách sử dụng dấu phẩy, chúng ta có thể dùng nhiều lệnh trong bất kì trường nào trong vòng for, như là trong phần khởi tạo. Ví dụ chúng ta có thể khởi tạo một lúc nhiều biến trong vòng lặp: for (n=0, i=100; n!=i; n++, i ) { //Thân của vòng lặp } 62
  64. Vòng lặp này sẽ thực hiện 50 lần nếu như n và i không bị thay đổi trong thân vòng lặp: Ví dụ 4.6: Viết chương trình in hình chữ nhật như sau #include #include #include void main() { int m,n,i,j; cout >m; cout >n; for (i=1;i Chức năng của nó đơn giản chỉ là lặp lại khi còn thoả mãn. Ví dụ 4.7: Viết một chương trình đếm ngược sử dụng vào lặp while: #include void main() { int n; cout "; cin >> n; 63
  65. while (n>0) { cout 8 8, 7, 6, 5, 4, 3, 2, 1, Khi chương trình chạy người sử dụng được yêu cầu nhập vào một số để đếm ngược. Sau đó, khi vòng lặp while bắt đầu nếu số mà người dùng nhập vào thoả mãn điều kiện điều kiện n>0 khối lệnh sẽ được thực hiện một số lần không xác định chừng nào điều kiện (n>0) còn được thoả mãn. Chúng ta cần phải nhớ rằng vòng lặp phải kết thúc ở một điểm nào đó, vì vậy bên trong vòng lặp chúng ta phải cung cấp một phương thức nào đó để buộc trở thành sai nếu không thì nó sẽ lặp lại mãi mãi. Trong ví dụ trên vòng lặp phải có lệnh n; để làm cho trở thành sai sau một số lần lặp. Ví dụ 4.8: Nhập số tự nhiên n. Tính S = n! #include #include #include void main() { int n,i,j; double s=1; cout >n; i=1; if(n==0) s=1; else while(i while (điều kiện); 64
  66. Chức năng của nó là hoàn toàn giống vòng lặp while chỉ trừ có một điều là điều kiện điều khiển vòng lặp được tính toán sau khi được thực hiện, vì vậy sẽ được thực hiện ít nhất một lần ngay cả khi không bao giờ được thoả mãn. Ví dụ 4.9 : Chương trình dưới đây sẽ viết ra bất kì số nào mà bạn nhập vào cho đến khi bạn nhập số 0. #include void main() { unsigned long n; do { cout > n; cout void main() { int n; for (n=10; n>0; n ) { cout<<n << ", "; 65
  67. if (n==3) { cout void main() { for (int n=10; n>0; n ) { if (n==5) continue; cout<<n << ", "; } cout<<"FIRE!"; } 66
  68. B. Phần thảo luận, bài tập Bài 1. Viết chương trình nhập vào số nguyên dương, in ra thông báo số chẵn hay lẻ. Bài 2. Viết chương trình nhập vào 4 số thực a, b, c, d. Tìm và in ra số lớn nhất trong 4 số đó (sử dụng toán tử điều kiện, và cấu trúc if). 2 Bài 3. Viết chương trình giải phương trình bậc 2: ax bx c 0 , với a, b, c nhập vào từ bàn phím (tính cả nghiệm phức). Bài 4. Viết chương trình nhập vào tháng, in ra tháng đó có bao nhiêu ngày. Bài 5. Viết chương trình nhập vào 2 số x, y và 1 trong 4 toán tử +, -, *, /. Nếu là + thì in ra kết quả x + y, nếu là – thì in ra x – y, nếu là * thì in ra x * y, nếu là / thì in ra x / y (nếu y = 0 thì thông báo không chia được). Bài 7. Viết chương trình nhập vào 3 số thực a, b, c. Kiểm tra xem a, b, c có phải là 3 cạnh của tam giác không? Nếu là 3 cạnh của tam giác thì tính diện tích của tam giác theo công thức sau: s p * ( p a) * ( p b) * p c) , với p (a b c) / 2 Hướng dẫn: a, b, c là 3 cạnh của tam giác phải thỏa điều kiện sau: (a + b) > c và (a + c) > b và (b + c) > a. Bài 8. Viết chương trình tính giá trị của hàm f, với x là số thực được nhập từ bàn phím. 0 x 0 2 f x x x 0 x 2 2 2 x sin x x 2 Bài 9. Viết chương trình tính tiền điện với chỉ số mới và chỉ số cũ được nhập vào từ bàn phím. In ra màn hình chỉ số cũ, chỉ số mới, và số tiền phải trả. Biết rằng 100 kWh đầu giá 550, từ KWh 101 - 150 giá 1.110, từ KWh 151 - 200 giá 1.470, từ KWh 201 - 300 giá 1.600, từ KWh 301 - 400 giá 1.720, từ KWh 401 trở lên giá 1.780. Bài 10. Viết chương trình nhập vào một số nguyên rồi in ra tất cả các ước số của số đó. Bài 11. Viết chương trình nhập vào một số và kiểm tra xem số đó có phải là số nguyên tố hay không? 67
  69. CHƯƠNG 5. HÀM TRONG C++ A. Phần lý thuyết 5.1. Hàm trong C++ Hàm là một khối lệnh được thực hiện khi nó được gọi từ một điểm khác của chương trình. Cú pháp: type ([tham số 1], [tham số 2], ) ; Trong đó: - type là kiểu dữ liệu được trả về của hàm. - là tên gọi của hàm. - [tham số i] là các tham số (có nhiều bao nhiêu cũng được tuỳ theo nhu cầu). Một tham số bao gồm tên kiểu dữ liệu sau đó là tên của tham số giống như khi khai báo biến (ví dụ int x) và đóng vai trò bên trong hàm như bất kì biến nào khác. Chúng dùng để truyền tham số cho hàm khi nó được gọi. Các tham số khác nhau được ngăn cách bởi các dấu phẩy. - là thân của hàm. Nó có thể là một lệnh đơn hay một khối lệnh. Ví dụ 5.1 Dưới đây là ví dụ đầu tiên về hàm #include int addition (int a, int b) { int r; r=a+b; return (r); } void main () { int z; z = addition (5,3); cout<<"z = " << z; } 68
  70. Kết quả: z = 8 Chúng ta có thể thấy hàm main bắt đầu bằng việc khai báo biến z kiểu int. Ngay sau đó là một lời gọi tới hàm addition. Nếu để ý chúng ta sẽ thấy sự tương tự giữa cấu trúc của lời gọi hàm với khai báo của hàm: Các tham số có vai trò thật rõ ràng. Bên trong hàm main chúng ta gọi hàm addition và truyền hai giá trị: 5 và 3 tương ứng với hai tham số int a và int b được khai báo cho hàm addition. Vào thời điểm hàm được gọi từ main, quyền điều khiển được chuyển sang cho hàm addition. Giá trị của c hai tham số (5 và 3) được copy sang hai biến cục bộ int a và int b bên trong hàm. Dòng lệnh sau: return (r); kết thúc hàm addition, và trả lại quyền điều khiển cho hàm nào đã gọi nó (main) và tiếp tục chương trình ở cái điểm mà nó bị ngắt bởi lời gọi đến addition. Nhưng thêm vào đó, giá trị được dùng với lệnh return (r) chính là giá trị được trả về của hàm. Giá trị trả về bởi một hàm chính là giá trị của hàm khi nó được tính toán. Vì vậy biến z sẽ có có giá trị được trả về bởi addition (5, 3), đó là 8. Phạm vi hoạt động của các biến Bạn cần nhớ rằng phạm vi hoạt động của các biến khai báo trong một hàm hay bất kì một khối lệnh nào khác chỉ là hàm đó hay khối lệnh đó và không thể sử dụng bên ngoài chúng. Trong chương trình ví dụ trên, bạn không thể sử dụng trực tiếp các biến a, b hay r trong hàm main vì chúng là các biến cục bộ của hàm addition. Thêm vào đó bạn cũng không thể sử dụng biến z trực tiếp bên trong hàm addition vì nó làm biến cục bộ của hàm main. Tuy nhiên bạn có thể khai báo các biến toàn cục để có thể sử dụng chúng ở bất kì đâu, bên trong hay bên ngoài bất kì hàm nào. Để làm việc này bạn cần khai báo 69
  71. chúng bên ngoài mọi hàm hay các khối lệnh, có nghĩa là ngay trong thân chương trình. Ví dụ 5.2 Đây là một ví dụ khác về hàm: #include int subtraction (int a, int b) { int r; r=a-b; return (r); } int main () { int x=5, y=3, z; z = subtraction (7,2); cout<<"Ket qua 1: "<<z<<'\n'; cout<<"Ket qua 2: "<<subtraction(7,2)<<'\n'; cout<<"Ket qua 3: "<<subtraction (x,y)<<'\n'; z= 4 + subtraction (x,y); cout << "Ket qua 4: " << z << '\n'; return 0; } Kết quả: Ket qua 1: 5 Ket qua 2: 5 Ket qua 3: 2 Ket qua 4: 6 Trong trường hợp này chúng ta tạo ra hàm subtraction. Chức năng của hàm này là lấy hiệu của hai tham số rồi trả về kết quả. Tuy nhiên, nếu phân tích hàm main các bạn sẽ thấy chương trình đã vài lần gọi đến hàm subtraction. Tôi đã sử dụng vài cách gọi khác nhau để các bạn thấy các cách khác nhau mà một hàm có thể được gọi. Để có hiểu cặn kẽ ví dụ này bạn cần nhớ rằng một lời gọi đến một hàm có thể hoàn toàn được thay thế bởi giá trị của nó. Ví dụ trong lệnh gọi hàm đầu tiên: z = subtraction (7,2); cout<<"Ket qua 1: "<<z<<'\n'; Nếu chúng ta thay lời gọi hàm bằng giá trị của nó (đó là 5), chúng ta sẽ có: z = 5; cout<<"Ket qua 1: "<<z<<'\n'; 70
  72. Tương tự như vậy cout<<"Ket qua 2: "<<subtraction(7,2)<<'\n'; Cũng cho kết quả giống như hai dòng lệnh trên nhưng trong trường hợp này chúng ta gọi hàm subtraction trực tiếp như là một tham số của cout. Chúng ta cũng có thể viết: cout<<"Ket qua 2: "<<5<<’\n’; Vì 5 là kết quả của subtraction (7,2). Còn với lệnh cout<<"Ket qua 3: " << subtraction (x,y)<<’\n’; Điều mới mẻ duy nhất ở đây là các tham số của subtraction là các biến thay vì các hằng. Điều này là hoàn toàn hợp lệ. Trong trường hợp này giá trị được truyền cho hàm subtraction là giá trị của x and y. Trường hợp thứ tư cũng hoàn toàn tương tự. Thay vì viết z = 4 + subtraction (x,y); chúng ta có thể viết: z = subtraction (x,y) + 4; Cũng hoàn toàn cho kết quả tương đương. 5.2. Truyền tham số cho hàm Cho đến nay, trong tất cả các hàm chúng ta đã biết, tất cả các tham số truyền cho hàm đều được truyền theo giá trị. Điều này có nghĩa là khi chúng ta gọi hàm với các tham số, những gì chúng ta truyền cho hàm là các giá trị chứ không phải bản thân các biến. Ví dụ, giả sử chúng ta gọi hàm addition như sau: int x=5, y=3, z; z = addition (x, y); Trong trường hợp này khi chúng ta gọi hàm addition thì các giá trị 5 and 3 được truyền cho hàm, không phải là bản thân các biến. Đến đây các bạn có thể hỏi tôi: Như vậy thì sao, có ảnh hưởng gì đâu? Điều đáng nói ở đây là khi các bạn thay đổi giá trị của các biến a hay b bên trong hàm thì các biến x và y vẫn không thay đổi vì chúng đâu có được truyền cho hàm chỉ có giá trị của chúng được truyền mà thôi. 71
  73. Hãy xét trường hợp bạn cần thao tác với một biến ngoài ở bên trong một hàm. Vì vậy bạn sẽ phải truyền tham số dưới dạng tham số biến như ở trong hàm duplicate trong ví dụ dưới đây: Ví dụ 5.3 #include void duplicate (int& a, int& b, int& c) { a*=2; b*=2; c*=2; } void main () { int x=1, y=3, z=7; duplicate (x, y, z); cout<<"x=" << x << ", y=" << y << ", z=" << z; } Kết quả: x=2, y=6, z=14 Điều đầu tiên làm bạn chú ý là trong khai báo của duplicate theo sau tên kiểu của mỗi tham số đều là dấu và (&), để báo hiệu rằng các tham số này được truyền theo tham số biến chứ không phải tham số giá trị. Khi truyền tham số dưới dạng tham số biến chúng ta đang truyền bản thân biến đó và bất kì sự thay đổi nào mà chúng ta thực hiện với tham số đó bên trong hàm sẽ ảnh hưởng trực tiếp đến biến đó. Trong ví dụ trên, chúng ta đã liên kết a, b và c với các tham số khi gọi hàm (x, y và z) và mọi sự thay đổi với a bên trong hàm sẽ ảnh hưởng đến giá trị của x và hoàn toàn tương tự với b và y, c và z. Kiểu khai báo tham số theo dạng tham số biến sử dụng dấu và (&) chỉ có trong C++. Trong ngôn ngữ C chúng ta phải sử dụng con trỏ để làm việc tương tự như thế. Truyền tham số dưới dạng tham số biến cho phép một hàm trả về nhiều hơn một giá trị. Ví dụ 5.4 72
  74. Đây là một hàm trả về số liền trước và liền sau của tham số đầu tiên. #include void prevnext (int x, int& prev, int& next) { prev = x-1; next = x+1; } void main (){ int x=100, y, z; prevnext (x, y, z); cout int divide (int a, int b=2) { int r; r=a/b; return (r); } void main () { cout<<divide (12); cout<<endl; cout<<divide (20,4); } Kết quả: 6 5 Nhưng chúng ta thấy trong thân chương trình, có hai lời gọi hàm divide. Trong lệnh đầu tiên: divide (12) 73
  75. Chúng ta chỉ dùng một tham số nhưng hàm divide cho phép đến hai. Bởi vậy hàm divide sẽ tự cho tham số thứ hai giá trị bằng 2 vì đó là giá trị mặc định của nó (chú ý phần khai báo hàm được kết thúc bởi int b=2). Vì vậy kết quả sẽ là 6 (12/2). Trong lệnh thứ hai: divide (20,4) Có hai tham số, bởi vậy giá trị mặc định sẽ được bỏ qua. Kết quả của hàm sẽ là 5 (20/4). 5.3. Đệ quy 5.3.1. Khái niệm đệ qui Một hàm gọi đến hàm khác là bình thường, nhưng nếu hàm lại gọi đến chính nó thì ta gọi hàm là đệ qui. Khi thực hiện một hàm đệ qui, hàm sẽ phải chạy rất nhiều lần, trong mỗi lần chạy chương trình sẽ tạo nên một tập biến cục bộ mới trên ngăn xếp (các đối, các biến riêng khai báo trong hàm) độc lập với lần chạy trước đó, từ đó dễ gây tràn ngăn xếp. Vì vậy đối với những bài toán có thể giải được bằng phương pháp lặp thì không nên dùng đệ qui. Để minh hoạ ta hãy xét hàm tính n giai thừa. Để tính n! ta có thể dùng phương pháp lặp như sau: Ví dụ 5.6 //Tính giai thừa #include void main() { int n; double kq = 1; cout > n; if(n==0) kq=1; else for (int i=1; i 0 Do đó ta có thể xây dựng hàm đệ qui tính n! như sau: 74
  76. Ví dụ 5.7 //Tính giai thừa theo đệ qui #include double gt(int n) { if (n==0) return 1; else return gt(n-1)*n; } void main() { int n; cout > n; cout<<gt(n); } Trong hàm main() giả sử ta nhập 3 cho n, khi đó để thực hiện câu lệnh cout<<gt(3) để in 3! đầu tiên chương trình sẽ gọi chạy hàm gt(3). Do 3 ≠ 0 nên hàm gt(3) sẽ trả lại giá trị gt(2)*3, tức lại gọi hàm gt với tham đối thực sự ở bước này là n = 2. Tương tự gt(2) = gt(1)*2 và gt(1) = gt(0)*1. Khi thực hiện gt(0) ta có đối n = 0 nên hàm trả lại giá trị 1, từ đó gt(1) = 1*1 = 1 và suy ngược trở lại ta có gt(2) = gt(1)*2 = 1*2 = 2, gt(3) = gt(2)*3 = 2*3 = 6, chương trình in ra kết quả 6. Từ ví dụ trên ta thấy hàm đệ qui có đặc điểm: Chương trình viết rất gọn, việc thực hiện gọi đi gọi lại hàm rất nhiều lần phụ thuộc vào độ lớn của đầu vào. Chẳng hạn trong ví dụ trên hàm được gọi n lần, mỗi lần như vậy chương trình sẽ mất thời gian để lưu giữ các thông tin của hàm gọi trước khi chuyển điều khiển đến thực hiện hàm được gọi. Mặt khác các thông tin này được lưu trữ nhiều lần trong ngăn xếp sẽ dẫn đến tràn ngăn xếp nếu n lớn. Tuy nhiên, đệ qui là cách viết rất gọn, dễ viết và đọc chương trình, mặt khác có nhiều bài toán hầu như tìm một thuật toán lặp cho nó là rất khó trong khi viết theo thuật toán đệ qui thì lại rất dễ dàng. 5.3.2. Lớp các bài toán giải được bằng đệ qui Phương pháp đệ qui thường được dùng để giải các bài toán có đặc điểm: - Giải quyết được dễ dàng trong các trường hợp riêng gọi là trường hợp suy biến hay cơ sở, trong trường hợp này hàm được tính bình thường mà không cần gọi lại chính nó, - Đối với trường hợp tổng quát, bài toán có thể giải được bằng bài toán cùng dạng nhưng với tham đối khác có kích thước nhỏ hơn tham đối ban đầu. Và sau một 75
  77. số bước hữu hạn biến đổi cùng dạng, bài toán đưa được về trường hợp suy biến. Như vậy trong trường hợp tính n! nếu n = 0 hàm cho ngay giá trị 1 mà không cần phải gọi lại chính nó, đây chính là trường hợp suy biến. Trường hợp n > 0 hàm sẽ gọi lại chính nó nhưng với n giảm 1 đơn vị. Việc gọi này được lặp lại cho đến khi n=0. Một lớp rất rộng của bài toán dạng này là các bài toán có thể định nghĩa được dưới dạng đệ qui như các bài toán lặp với số bước hữu hạn biết trước, các bài toán UCLN, tháp Hà Nội, 5.3.3. Cấu trúc chung của hàm đệ qui Cú pháp: if (trường hợp suy biến) { trình bày cách giải //Giả định đã có cách giải } else //Trường hợp tổng quát { gọi lại hàm với tham đối "bé" hơn } Ví dụ 5.8 Tìm UCLN của 2 số a, b. Bài toán có thể được định nghĩa dưới dạng đệ qui như sau: - nếu a = b thì UCLN = a - nếu a > b thì UCLN(a, b) = UCLN(a-b, b) - nếu a int UCLN(int a, int b) //qui uoc a, b > 0 { if (a b) UCLN(a-b, b); } void main() { 76
  78. int x,y; cout >x; cout >y; cout long Fib(int n) { long kq; if (n==0 || n==1) kq = 1; else kq = Fib(n-1) + Fib(n-2); return kq; } void main() { int n; cout >n; cout ( ) { } 77
  79. Lời gọi hàm cũng như bất kì một hàm nào khác. Không cần thiết phải đặt từ khoá inline trong lệnh gọi, chỉ cần trong lời khai báo hàm là đủ. 5.5. Hàm tải bội Hai hàm có thể có cũng tên nếu khai báo tham số của chúng khác nhau, điều này có nghĩa là bạn có thể đặt cùng một tên cho nhiều hàm nếu chúng có số tham số khác nhau hay kiểu dữ liệu của các tham số khác nhau (hay thậm chí là kiểu dữ liệu trả về khác nhau). Ví dụ 5.10 #include int divide (int a, int b) { return (a/b); } float divide (float a, float b) { return (a/b); } void main () { int x=5,y=2; float n=5.0,m=2.0; cout<<divide (x,y); cout<<"\n"; cout<<divide (n,m); } Kết quả: 2 2.5 Trong ví dụ này chúng ta định nghĩa hai hàm có cùng tên nhưng một hàm dùng hai tham số kiểu int và hàm còn lại dùng kiểu float. Trình biên dịch sẽ biết cần phải gọi hàm nào bằng cách phân tích kiểu tham số khi hàm được gọi. Để đơn giản tôi viết cả hai hàm đều có mã lệnh như nhau nhưng điều này không bắt buộc. Bạn có thể xây dựng hai hàm có cùng tên nhưng hoạt động hoàn toàn khác nhau. 78
  80. B. Phần thảo luận, bài tập Bài 1. Viết hàm tính N!, với N nguyên dương nhập vào từ bàn phím. Bài 2. Xây dựng các hàm S1, S2, S3, S4, S5, S6, S7, S8 để tính giá trị của biểu thức tương ứng dưới đây (sử dụng các cấu trúc lặp for, while, do while). Sử dụng các hàm nói trên để tính giá trị các biểu thức, với n>0 được nhập vào từ bàn phím. 1 1 ( 1) n 1 S1 1 12 22 12 22 32 12 22 32 n 2 2 22 23 ( 2) n S2 1 1! 2! 3! n! S3 x x x x x  n 1 1 1 1 1 S4 ( 1)n 1 2 4 6 8 2n 1 1 1 S5 1 12 22 12 22 32 12 22 32 n 2 1 1 ( 1)n 1 S6 1 1 2 1 2 3 1 2 3 n 2 22 23 2n S7 1 1! 2! 3! n! 1 1 1 1 1 S8 ( 1)n 1 1! 2! 3! 4! n! 79
  81. CHƯƠNG 6. CÁC KIỂU DỮ LIỆU CÓ CẤU TRÚC A. Phần lý thuyết 6.1. Mảng dữ liệu 6.1.1. Mảng một chiều a) Khái niệm về mảng Khi cần lưu trữ một dãy n phần tử dữ liệu chúng ta cần khai báo n biến tương ứng với n tên gọi khác nhau. Điều này sẽ rất khó khăn cho người lập trình để có thể nhớ và quản lý hết được tất cả các biến, đặc biệt khi n lớn. Trong thực tế, hiển nhiên chúng ta gặp rất nhiều dữ liệu có liên quan đến nhau về một mặt nào đó, ví dụ chúng có cùng kiểu và cùng thể hiện một đối tượng: như các toạ độ của một vectơ, các số hạng của một ma trận, các sinh viên của một lớp hoặc các dòng kí tự của một văn bản Lợi dụng đặc điểm này toàn bộ dữ liệu (cùng kiểu và cùng mô tả một đối tượng) có thể chỉ cần chung một tên gọi để phân biệt với các đối tượng khác, và để phân biệt các dữ liệu trong cùng đối tượng ta sử dụng cách đánh số thứ tự cho chúng, từ đó việc quản lý biến sẽ dễ dàng hơn, chương trình sẽ gọn và có tính hệ thống hơn. Giả sử ta có 2 vectơ trong không gian ba chiều, mỗi vec tơ cần 3 biến để lưu 3 toạ độ, vì vậy để lưu toạ độ của 2 vectơ chúng ta phải dùng đến 6 biến, ví dụ x1, y1, z1 cho vectơ thứ nhất và x2, y2, z2 cho vectơ thứ hai. Một kiểu dữ liệu mới được gọi là mảng một chiều cho phép ta chỉ cần khai báo 2 biến v1 và v2 để chỉ 2 vectơ, trong đó mỗi v1 hoặc v2 sẽ chứa 3 dữ liệu được đánh số thứ tự từ 0 đến 2, trong đó ta có thể ngầm định thành phần 0 biểu diễn toạ độ x, thành phần 1 biểu diễn toạ độ y và thành phần có số thứ tự 2 sẽ biểu diễn toạ độ z. Tóm lại, mảng là một dãy các thành phần có cùng kiểu được sắp kề nhau liên tục trong bộ nhớ. Tất cả các thành phần đều có cùng tên là tên của mảng. Để phân biệt các thành phần với nhau, các thành phần sẽ được đánh số thứ tự từ 0 cho đến hết mảng. Khi cần nói đến thành phần cụ thể nào của mảng ta sẽ dùng tên mảng và kèm theo số thứ tự của thành phần đó. Dưới đây là hình ảnh của một mảng gồm có 9 thành phần, các thành phần được đánh số từ 0 đến 8. 0 1 2 3 4 5 6 7 8 80
  82. b) Khai báo mảng Cú pháp: [số thành phần]; [số thành phần]={ dãy giá trị }; [ ] = { dãy giá trị }; - Tên kiểu là kiểu dữ liệu của các thành phần, các thành phần này có kiểu giống nhau. Thỉnh thoảng ta cũng gọi các thành phần là phần tử. - Cách khai báo trên giống như khai báo tên biến bình thường nhưng thêm số thành phần trong mảng giữa cặp dấu ngoặc vuông [] còn được gọi là kích thước của mảng. Mỗi tên mảng là một biến và để phân biệt với các biến thông thường ta còn gọi là biến mảng. - Một mảng dữ liệu được lưu trong bộ nhớ bởi dãy các ô liên tiếp nhau. Số lượng ô bằng với số thành phần của mảng và độ dài (byte) của mỗi ô đủ để chứa thông tin của mỗi thành phần. Ô đầu tiên được đánh thứ tự bởi 0, ô tiếp theo bởi 1, và tiếp tục cho đến hết. Như vậy nếu mảng có n thành phần thì ô cuối cùng trong mảng sẽ được đánh số là n – 1. - Dạng khai báo thứ 2 cho phép khởi tạo mảng bởi dãy giá trị trong cặp dấu {}, mỗi giá trị cách nhau bởi dấu phảy (,), các giá trị này sẽ được gán lần lượt cho các phần tử của mảng bắt đầu từ phần tử thứ 0 cho đến hết dãy. Số giá trị có thể bé hơn số phần tử. Các phần tử mảng chưa có giá trị sẽ không được xác định cho đến khi trong chương trình nó được gán một giá trị nào đó. - Dạng khai báo thứ 3 cho phép vắng mặt số phần tử, trường hợp này số phần tử được xác định bởi số giá trị của dãy khởi tạo. Do đó nếu vắng mặt cả dãy khởi tạo là không được phép (chẳng hạn khai báo int a[] là sai). Ví dụ: Khai báo biến chứa 2 vectơ a, b trong không gian 3 chiều: float a[3], b[3]; Khai báo 3 phân số a, b, c; trong đó a = 1/3 và b = 3/5: int a[2] = {1, 3}, b[2] = {3, 5}, c[2]; ở đây ta ngầm qui ước thành phần đầu tiên (số thứ tự 0) là tử và thành phần thứ hai (số thứ tự 1) là mẫu của phân số. Khai báo mảng L chứa được tối đa 100 số nguyên dài: long L[100]; 81
  83. Khai báo mảng dong (dòng), mỗi dòng chứa được tối đa 80 kí tự: char dong[80]; Khai báo dãy Data chứa được 5 số thực độ chính xác gấp đôi: double Data[] = { 0,0,0,0,0 }; //Khởi tạo tạm thời bằng 0 c) Cách sử dụng - Để chỉ thành phần thứ i (hay chỉ số i) của một mảng ta viết tên mảng kèm theo chỉ số trong cặp ngoặc vuông []. - Tuy mỗi mảng biểu diễn một đối tượng nhưng chúng ta không thể áp dụng các thao tác lên toàn bộ mảng mà phải thực hiện thao tác thông qua từng thành phần của mảng. Ví dụ chúng ta không thể nhập dữ liệu cho mảng a[10] bằng câu lệnh: cin >> a; //sai mà phải nhập cho từng phần tử từ a[0] đến a[9] của mảng a. Dĩ nhiên trong trường hợp này chúng ta phải cần đến lệnh lặp for: int i; for (i = 0; i > a[i]; Ví dụ 6.1 //Tìm tổng, tích 2 phân số. #include void main() { int a[2], b[2], tong[2], tich[2]; cout > a[0]; cout > a[1]; cout > b[0]; cout > b[1]; tong[0] = a[0]*b[1] + a[1]*b[0]; tong[1] = a[1] * b[1]; tich[0] = a[0]*b[0]; tich[1] = a[1] * b[1]; cout void main() { 82
  84. float a[50], sd, sa, s0; //a chứa tối đa 50 số int i, n; cout > n; //nhập số phần tử for (i=0; i > a[i]; } //nhập dãy số sd = sa = s0 = 0; for (i=1; i 0) sd++; if (a[i] < 0) sa++; if (a[i] == 0) s0++; } cout<<“Số số dương = “ << sd << “ số số âm = “ << sa; cout<<“Số số bằng 0 = “ << s0; } 6.1.2. Mảng nhiều chiều Để thuận tiện trong việc biểu diễn các loại dữ liệu phức tạp như ma trận hoặc các bảng biểu có nhiều chỉ tiêu, C++ đưa ra kiểu dữ liệu mảng nhiều chiều. Tuy nhiên, việc sử dụng mảng nhiều chiều rất khó lập trình vì vậy trong mục này chúng ta chỉ bàn đến mảng hai chiều. Đối với mảng một chiều m thành phần, nếu mỗi thành phần của nó lại là mảng một chiều n phần tử thì ta gọi mảng là hai chiều với số phần tử (hay kích thước) mỗi chiều là m và n. Ma trận là một minh hoạ cho hình ảnh của mảng hai chiều, nó gồm m dòng và n cột, tức chứa m x n phần tử, và hiển nhiên các phần tử này có cùng kiểu. Tuy nhiên, về mặt bản chất mảng hai chiều không phải là một tập hợp với m x n phần tử cùng kiểu mà là tập hợp với m thành phần, trong đó mỗi thành phần là một mảng một chiều với n phần tử. Điểm nhấn mạnh này sẽ được giải thích cụ thể hơn trong các phần trình bày về con trỏ của chương sau. Ví dụ nhiệt độ trung bình theo mùa của ba thành phố: Hà Nội, Thái Nguyên, Cao Bằng Mùa xuân Mùa hạ Mùa thu Mùa đông Hà Nội 25 34 22 17 Thái Nguyên 24 33 21 16 Cao bằng 23 32 20 10 Điều này có thể được biểu diễn bằng một mảng hai chiều mà mỗi phần tử mảng là một số nguyên: int a[3][4]; 83