Tuy nhiên, trong lập trình cổ điển với mỗi kiểu dữ liệu ta phải viết một hàm khác nhau (chỉ để khai báo kiểu đối hoặc kiểu giá trị trả lại của hàm) để thể hiện thuật toán. Điều này gây l[r]
(1)dành
cho
hội
đồng
nghiệm
thu
GIÁO TRÌNH LẬP TRÌNH NÂNG CAO
(2)dành
cho
hội
đồng
nghiệm
(3)dành
cho
hội
đồng
nghiệm
thu
MỤC LỤC
1 Mở đầu 1
1.1 Giải tốn lập trình
1.1.1 Thuật toán
1.1.2 Thiết kế chương trình
1.1.3 Chu kỳ phát triển phần mềm
1.2 Tiêu chuẩn đánh giá chương trình tốt
1.3 Ngơn ngữ lập trình chương trình dịch
1.4 Mơi trường lập trình bậc cao
1.5 Lịch sử C C++
1.6 Chương trình C++: In dòng văn
2 Một số khái niệm C++ 11 2.1 Khai báo biến sử dụng biến 11
2.1.1 Biến 11
2.1.2 Tên hay định danh 12
2.1.3 Câu lệnh gán 14
2.2 Vào liệu 15
2.2.1 Xuất liệu với cout 15
2.2.2 Chỉ thị biên dịch không gian tên 17
2.2.3 Các chuỗi Escape 18
2.2.4 Nhập liệu với cin 18
2.3 Kiểu liệu biểu thức 20
2.3.1 Kiểu int kiểu double 20
2.3.2 Các kiểu số khác 22
2.3.3 Kiểu C++11 22
2.3.4 Kiểu char 24
2.3.5 Tương thích kiểu liệu 25
2.3.6 Toán từ số học biểu thức 26
2.4 Luồng điều khiển 28
(4)dành
cho
hội
đồng
nghiệm
thu
ii MỤC LỤC
2.6 Biên dịch chương trình với GNU/C++ 32
3 Kiểm thử gỡ rối chương trình 37 3.1 Kỹ thuật kiểm thử 37
3.1.1 Kiểm thử viết mã nguồn 38
3.2 Kỹ thuật gỡ rối chương trình 39
3.2.1 Khái niệm vế gỡ rối chương trình 39
3.2.2 Phân loại lỗi 39
3.2.3 Một số kỹ thuật gỡ rối 40
3.2.4 Giải pháp vấn đề liên quan đến C/C++ 42
3.3 Lập trình không lỗi 44
4 Hàm 47 4.1 Thiết kế từ xuống (top-down) 47
4.2 Hàm 48
4.2.1 Ý nghĩa hàm 48
4.2.2 Cấu trúc chung hàm 48
4.2.3 Khai báo hàm 51
4.3 Cách sử dụng hàm 52
4.3.1 Lời gọi hàm 52
4.3.2 Hàm với đối mặc định 54
4.4 Biến toàn cục biến địa phương 55
4.4.1 Biến địa phương (biến hàm, khối lệnh) 55
4.4.2 Biến toàn cục (biến tất hàm) 56
4.4.3 Mức ưu tiên biến toàn cục địa phương 56
4.5 Tham đối chế truyền giá trị cho tham đối 60
4.5.1 Truyền theo tham trị 60
4.5.2 Biến tham chiếu 61
4.5.3 Truyền theo tham chiếu 63
4.5.4 Hai cách truyền giá trị cho hàm từ khóa const 64
4.6 Ngăn xếp gọi hàm mẫu tin kích hoạt 64
4.7 Chồng hàm khuôn mẫu hàm 68
4.7.1 Chồng hàm (hàm trùng tên) 68
4.7.2 Khuôn mẫu hàm 70
4.8 Lập trình với hàm đệ quy 72
4.8.1 Khái niệm đệ qui 72
4.8.2 Lớp toán giải đệ qui 74
(5)dành
cho
hội
đồng
nghiệm
thu
MỤC LỤC iii
5 Mảng 83
5.1 Lập trình thao tác với mảng chiều 83
5.1.1 Ý nghĩa mảng 83
5.1.2 Thao tác với mảng chiều 84
5.1.3 Mảng hàm 88
5.1.4 Tìm kiếm xếp 93
5.2 Lập trình thao tác với mảng nhiều chiều 98
5.2.1 Mảng chiều 98
5.2.2 Thao tác với mảng hai chiều 99
5.3 Lập trình thao tác với xâu kí tự 105
5.3.1 Khai báo 106
5.3.2 Thao tác với xâu kí tự 106
5.3.3 Phương thức nhập xâu (#include <iostream>) 107
5.3.4 Một số hàm làm việc với xâu kí tự (#include <cstring>) 108
5.3.5 Các hàm chuyển đổi xâu dạng số thành số (#include <cstdlib>) 112
5.3.6 Một số ví dụ làm việc với xâu 113
6 Các kiểu liệu trừu tượng 119 6.1 Kiểu liệu trừu tượng cấu trúc (struct) 119
6.1.1 Khai báo, khởi tạo 119
6.1.2 Hàm cấu trúc 122
6.1.3 Bài toán Quản lý sinh viên (QLSV) 127
6.2 Kiểu liệu trừu tượng lớp (class) 134
6.2.1 Khai báo lớp 135
6.2.2 Sử dụng lớp 136
6.2.3 Bài toán Quản lý sinh viên 143
6.2.4 Khởi tạo (giá trị ban đầu) cho đối tượng 146
6.2.5 Hủy đối tượng 152
6.2.6 Hàm bạn (friend function) 152
6.2.7 Tạo phép toán cho lớp (hay tạo chồng phép toán - Operator Overloading) 156 6.3 Dạng khuôn mẫu hàm lớp 159
6.3.1 Khai báo kiểu mẫu 159
6.3.2 Sử dụng kiểu mẫu 160
6.3.3 Một số dạng mở rộng khai báo mẫu 162
7 Con trỏ nhớ 167 7.1 Khái niệm trỏ 167
7.2 Biến trỏ 167
(6)dành
cho
hội
đồng
nghiệm
thu
iv MỤC LỤC
7.4 Con trỏ mảng động 172
7.4.1 Biến mảng biến trỏ 172
7.4.2 Biến mảng động 174
7.5 Truyền tham số hàm trỏ 176
7.6 Con trỏ hàm 177
7.7 Lập trình với danh sách liên kết 178
7.7.1 Nút danh sách liên kết 180
7.7.2 Danh sách liên kết lớp 192
8 Vào liệu 197 8.1 Dòng vào file 197
8.2 Vào file 198
8.2.1 Mở file 198
8.2.2 Đóng file 199
8.3 Vào với file văn 199
8.4 Vào với file nhị phân 201
8.5 Truy cập ngẫu nhiên 202
9 Xử lý ngoại lệ 205 9.1 Các vấn đề xử lý ngoại lệ 205
9.1.1 Ví dụ xử lý ngoại lệ 205
9.1.2 Định nghĩa lớp ngoại lệ 207
9.1.3 Ném bắt nhiều ngoại lệ 208
9.1.4 Ném ngoại lệ từ hàm 209
9.1.5 Mô tả ngoại lệ 211
9.2 Kỹ thuật lập trình cho xử lý ngoại lệ 211
9.2.1 Ném ngoại lệ đâu 211
9.2.2 Cây phả hệ ngoại lệ STL 212
9.2.3 Kiểm tra nhớ 213
10 Tiền xử lý lập trình nhiều file 215 10.1 Các thị tiền xử lý 215
10.1.1 Chỉ thị bao hàm tệp #include 215
10.1.2 Chỉ thị macro #define 216
10.1.3 Các thị biên dịch có điều kiện #if, #ifdef, #ifndef 217
10.2 Lập trình nhiều file 219
10.2.1 Tổ chức chương trình 219
10.2.2 Viết kiểm tra file include 220
(7)dành
cho
hội
đồng
nghiệm
thu
MỤC LỤC v
11 Lập trình với thư viện chuẩn STL 225
11.1 Giới thiệu thư viện chuẩn STL 225
11.2 Khái niệm trỏ duyệt 225
11.2.1 Các thao tác với trỏ duyệt 226
11.2.2 Các loại trỏ duyệt 228
11.3 Khái niệm vật chứa 230
11.3.1 Các vật chứa dạng dãy 231
11.3.2 Ngăn xếp hàng đợi 236
11.3.3 Tập hợp ánh xạ 239
11.3.4 Hàm băm, tập hợp ánh xạ không thứ tự (C++11) 242
11.4 Các thuật toán mẫu 244
11.4.1 Thời gian chạy ký hiệu “O-lớn” 244
11.4.2 Các thuật tốn khơng thay đổi vật chứa 245
11.4.3 Các thuật toán thay đổi vật chứa 248
11.4.4 Các thuật toán tập hợp 250
11.5 Một số thư viện chuẩn khác STL 250
11.5.1 Xử lý xâu với <string> 251
11.5.2 Con trỏ thông minh quản lý nhớ với <memory> (C++11) 254
11.5.3 Tính toán thời gian với <chrono> (C++11) 255
11.5.4 Lập trình song song với <thread> (C++11) 256
A Bảng từ khóa ngơn ngữ C++ 261
B Thứ tự ưu tiên phép tốn 263
C Phong cách lập trình 265
D Hàm inline 269
(8)dành
cho
hội
đồng
nghiệm
thu
vi MỤC LỤC
Lời giới thiệu
Lập trình cách thức diễn tả thuật tốn (chương trình) giải vấn đề cho máy tính hiểu thi hành thuật tốn Nằm bắt hiểu rõ kỹ thuật lập trình giúp viết chương trình hiệu phát sinh lỗi Hơn nữa, thuật toán tốn hiểu cặn kẽ thơng qua chương trình thể thuật tốn máy tính
Giáo trình Lập trình nâng cao cung cấp nội dung nâng cao kỹ thuật lập trình cho sinh viên đại học chuyên ngành CNTT u thích lập trình Giáo trình giới thiệu kiến thức ngơn ngữ C++ sử dụng ngơn ngữ lập trình để minh họa ví dụ kỹ thuật lập trình Giáo trình thích hợp cho có kiến thức lập trình
Cách tiếp cận viết giáo trình trình bày kỹ thuật lập trình để giải tốn khơng sâu giới thiệu ngơn ngữ lập trình Hơn giáo trình thiết kế dành cho bạn sinh viên có kiến thức lập trình, ví dụ học qua mơn học nhập mơn lập trình Do đó, giáo trình đề cập kiến thức ngôn ngữ C++ để minh họa kỹ thuật lập trình
Nội dung giáo trình đề cập kỹ thuật lập trình đến nâng cao giúp sinh viên lập trình giải toán cách hiệu giảm thiểu mắc lỗi chương trình Các chương giáo trình bao gồm sau: Chương giới thiệu bước giải giải tốn lập trình ngơn ngữ lập trình bậc cao C++ Chương trình bày khái niệm C++ Kiểm thử gỡ rối kỹ thuật quan trọng trình lập trình Vì vậy, vấn đề đề cập chương giáo trình Chương 4, đề cập đến lập trình sử dụng hàm mảng C++ Chương giới thiệu kiểu liệu trừu tượng Chương trình bày trỏ, nhớ kỹ thuật lập trình dựa vào trỏ Chương 8, trình bày thao tác vào liệu cách xử lý ngoại lệ C++ Nhằm cung cấp kỹ thuật lập trình để phát triển dự án lớn phức tạp, chương 10 cung cấp kiến thức tiền xử lý lập trình nhiều file Chương cuối trình bày thư viện chuẩn tiếng STL C++ cách thức lập trình sử dụng thư viện Ngồi cịn có phụ lục cuối giáo trình: bảng từ khóa C++, thứ tự ưu tiên phép tốn, phong cách lập trình
Các tác giả chân thành cảm ơn TS Trần Thi Minh Châu, TS Lê Quang Minh, ThS Trần Hồng Việt, ThS Phạm Nghĩa Luân, ThS Nguyễn Quang Huy đồng nghiệp sinh viên khoa CNTT, Trường Đại học Cơng nghệ đọc thảo đóng góp ý kiến quí báu nội dung hình thức trình bày Đây lần xuất nên chắn giảo trình cịn nhiều khiếm khuyết, chúng tơi mong nhận ý kiến góp ý để giáo trình hồn thiện
(9)dành
cho
hội
đồng
nghiệm
thu
Chương 1 Mở đầu
Trong chương này, mô tả thành phần máy tính kỹ thuật thiết kế viết chương trình máy tính Tiếp theo, chúng tơi minh họa chương trình đơn giản ngơn ngữ C++ mơ tả chúng hoạt động
1.1 Giải tốn lập trình
Trong phần này, mô tả số nguyên lý chung để sử dụng thiết kế viết chương trình máy tính Đây nguyên lý tổng quát ta sử dụng cho ngơn ngữ lập trình không ngôn ngữ C++
1.1.1 Thuật tốn
Khi học ngơn ngữ lập trình đầu tiên, thường dễ nhận cơng việc khó khăn giải tốn máy tính chuyển ý tưởng thành ngôn ngữ cụ thể để đưa vào máy tính Phần khó khăn giải tốn máy tính tìm giải pháp Sau tìm giải pháp, cơng việc thường lệ chuyển giải pháp tốn thành ngơn ngữ yêu cầu, C++ số ngơn ngữ lập trình khác Vì vậy, điều hữu ích tạm thời bỏ qua ngơn ngữ lập trình thay vào tập trung xây dựng bước giải pháp viết chúng ngôn ngữ tự nhiên (tiếng Việt, tiếng Anh, …) Dãy bước giải pháp hiểu thuật tốn
Dãy thị xác mà đưa giải pháp gọi thuật toán Thuật toán biểu diễn dạng ngơn ngữ tự nhiên ngơn ngữ lập trình C++ Một chương trình máy tính đơn giản thuật tốn biểu diễn ngơn ngữ mà máy tính hiểu thi hành Vì vậy, khái niệm thuật tốn tổng quát so với khái niệm chương trình
(10)dành
cho
hội
đồng
nghiệm
thu
2 Mở đầu
Thuật toán xác định số lần tên xuất danh sách tên cho trước
1 Lấy danh sách tên Lấy tên cần tính
3 Thiết lập SOLANTEN
4 Thực với tên danh sách tên
So sánh tên với tên cần tính, tên SOLANTEN tăng thêm
5 Thông báo số lần tên cần tính SOLANTEN
Hình 1.1: Thuật tốn 1.1.2 Thiết kế chương trình
Thiết kế chương trình thường nhiệm vụ khó Khơng có tập đầy đủ qui tắc, thuật tốn để nói với viết chương trình Tuy nhiên, có q trình thiết kế chương trình tương đối tổng qt mơ tả hình 1.2 Tồn việc thiết kế chương trình chia làm hai pha: pha giải toán pha thực Kết pha giải toán thuật tốn tốn biểu diễn dạng ngơn ngữ tự nhiên Để có chương trình ngơn ngữ lập trình C++, thuật tốn chuyển đổi ngơn ngữ lập trình Q trình xây dựng chương trình từ thuật toán gọi pha thực
Bước định nghĩa toán , chắn tốn mà chương trình cần giải Chúng ta cần mơ tả đầy đủ xác Chúng ta cần định nghĩa toán dạng tin học Xác định rõ ràng buộc, liệu đầu vào đầu toán kiểu liệu cần xử lý Ví dụ, chương chương trình kế tốn ngân hàng, phải biết khơng lãi suất mà lãi suất cộng dồn từ hàng năm, hàng tháng, hàng ngày hay không
Pha thực bước đơn giản Có chi tiết quan tâm đơi có số chi tiết tối ưu thực tinh tế đơn giản so với pha Khi thành thạo với ngôn ngữ C++ ngơn ngữ lập trình khác, việc chuyển đổi từ thuật tốn sang chương trình ngơn ngữ lập trình trở thành cơng việc bình thường
Như đề cập hình 1.2, kiểm thử xảy pha Trước chương trình viết, thuật tốn cần kiểm thử Khi thuật toán chưa hiệu quả, cần thiết kế thuật tốn lại Kiểm thử thủ cơng bước thực thi hành thuật
Hình 1.1: Thuật tốn
1.1.2 Thiết kế chương trình
Thiết kế chương trình thường nhiệm vụ khó Khơng có tập đầy đủ qui tắc, thuật tốn để nói với viết chương trình Tuy nhiên, có q trình thiết kế chương trình tương đối tổng qt mơ tả hình1.2 Tồn việc thiết kế chương trình chia làm hai pha: pha giải toán pha thực Kết pha giải toán thuật tốn tốn biểu diễn dạng ngơn ngữ tự nhiên Để có chương trình ngơn ngữ lập trình C++, thuật tốn chuyển đổi ngơn ngữ lập trình Q trình xây dựng chương trình từ thuật toán gọi pha thực
Bước định nghĩa toán , chắn toán mà chương trình cần giải Chúng ta cần mơ tả đầy đủ xác Chúng ta cần định nghĩa toán dạng tin học Xác định rõ ràng buộc, liệu đầu vào đầu toán kiểu liệu cần xử lý Ví dụ, chương trình kế tốn ngân hàng, phải biết khơng lãi suất mà lãi suất cộng dồn từ hàng năm, hàng tháng, hàng ngày hay không
Pha thực khơng phải bước đơn giản Có chi tiết quan tâm đơi có số chi tiết tối ưu thực tinh tế đơn giản so với pha Khi thành thạo với ngôn ngữ C++ ngôn ngữ lập trình khác, việc chuyển đổi từ thuật tốn sang chương trình ngơn ngữ lập trình trở thành cơng việc bình thường
Như đề cập hình 1.2, kiểm thử xảy pha Trước chương trình viết, thuật tốn cần kiểm thử Khi thuật toán chưa hiệu quả, cần thiết kế thuật toán lại Kiểm thử thủ công bước thực thi hành thuật tốn làm Chương trình C++ kiểm thử cách dịch chạy với số liệu đầu vào Trình biên dịch cho thơng báo lỗi với số loại lỗi cụ thể
1.1.3 Chu kỳ phát triển phần mềm
Thiết kế hệ thống phần mềm lớn trình biên dịch hệ điều hành thường chia quy trình phát triển phần mềm thành sáu pha biết chu kỳ phát triển phần mềm Sáu pha sau:
(11)dành
cho
hội
đồng
nghiệm
thu
1.2 Tiêu chuẩn đánh giá chương trình tốt 3
tốn làm Chương trình C++ kiểm thử cách dịch nó chạy với số liệu đầu vào Trình biên dịch cho thơng báo lỗi với số loại lỗi cụ thể
Định nghĩa tốn Pha giải toán
Thiết kế thuật toán Kiểm thử
thủ công
Dịch sang C++
Kiểm thử Bắt đầu
Chương trình Pha thực thi
Hình 1.2: Quá trình thiết kế chương trình
1.1.3 Chu kỳ phát triển phần mềm
Thiết kế hệ thống phần mềm lớn trình biên dịch hệ điều hành thường chia qui trình phát triển phần mềm thành sáu pha biết chu kỳ phát triển phần mềm Sáu pha sau:
1 Phân tích đặc tả toán (định nghĩa toán) 2 Thiết kế phần mềm (thiết kế thuật toán đối tượng) 3 Lập trình
4 Kiểm thử
5 Bảo trì nâng cấp hệ thống phần mềm 6 Hủy không dùng
Hình 1.2: Quá trình thiết kế chương trình Lập trình
4 Kiểm thử
5 Bảo trì nâng cấp hệ thống phần mềm Hủy không dùng
1.2 Tiêu chuẩn đánh giá chương trình tốt
Như mơt chương trình tốt có lẽ chủ đề tranh luận chưa nguội từ người bắt đầu lập trình cho máy tính Có thể nói, viết chương trình tốt nghệ thuật qua kinh nghiệm chúng tơi, chương trình tốt thường có đặc điểm sau:
1 Dễ đọc: Mã nguồn chương trình tốt phải giúp lập trình viên (cả người viết chương trình, người nhóm, người bảo trì chương trình) đọc chúng cách dễ dàng Luồng điều khiển chương trình phải rõ ràng, khơng làm khó cho người đọc Nói mội cách khác, chương trình tốt có khả giao tiếp với người đọc chúng.
2 Dễ kiểm tra: Các mô-đun, hàm chương trình viết cho chúng dễ dàng đặt vào kiểm tra đơn vị chương trình (unit test).
(12)dành
cho
hội
đồng
nghiệm
thu
4 Mở đầu
4 Dễ mở rộng: Khi cần thêm chức tính mới, người viết chương trình dễ dàng viết tiếp mã nguồn để thêm vào mã nguồn cũ Người mở rộng chương trình (có thể khơng phải người lập trình đầu tiên) khó “làm sai” mở rộng mã nguồn một chương trình tốt
Tất nhiên, tất đặc điểm đặc điểm lý tưởng chương trình tốt Khi phát triển chương trình hoăc phần mềm, điều kiện thực tế ảnh hưởng nhiều khả đạt đặc điểm chương trình hồn hảo Ví dụ, đến hạn báo cáo nộp chương trình cho đối tác, khơng kịp kiểm tra hết tính Hoặc bỏ qua nhiều bước tối ưu mã nguồn làm cho mã nguồn sáng, dễ hiểu Thực tế làm phần mềm trình cân lý tưởng (4 đặc điểm trên) yêu cầu khác Hiếm thỏa mãn đặc điểm chúng ln đích chúng ta, lập trình viên tương lai hướng tới
1.3 Ngơn ngữ lập trình chương trình dịch
Có nhiều ngơn ngữ lập trình để viết chương trình Trong giáo trình này, chúng tơi giới thiệu và sử dụng ngơn ngữ lập trình C++ để viết chương trình C++ ngơn ngữ lập trình bậc cao sử dụng rộng rãi thực tế để phát triển phần mềm Ngồi cịn có ngơn ngữ bậc cao thông dụng C, C#, Java, Python, PHP, Pascal Ngơn ngữ lập trình bậc cao gần với ngôn ngữ tự nhiên người Chúng thiết kế để người dễ dàng viết chương trình người dễ dàng đọc chương trình viết Ngơn ngữ bậc cao C++, bao gồm thị phức tạp nhiều so với thị đơn giản mà vi xử lý máy tính hiểu thi hành Điều để phân biệt với loại ngôn ngữ mà máy tính hiểu thường gọi ngôn ngữ bậc thấp.
Ngôn ngữ bậc thấp bao gồm ngôn ngữ máy ngôn ngữ Assembly Ngôn ngữ máy bao gồm thị phần cứng tạo nên máy hiểu Cịn ngôn ngữ Assembly sử dụng tập lệnh qui tắc tiếng Anh đơn giản để biểu diễn Ngơn ngữ Assemby gần ngơn ngữ máy cần dịch chương trình dịch đơn giản để thành ngơn ngữ máy
1.4 Mơi trường lập trình bậc cao
Phần trình bày bước để xây dựng thi hành chương trình C++ sử dụng mơi trường phát triển C++ (minh họa hình1.3), hệ thống C++ bao gồm ba phần: môi trường phát triển, ngơn ngữ thư viện chuẩn C++ Chương trình C++ có pha: Soạn thảo, tiền xử lý, Dịch, liên kết, nạp thi hành Dưới mô tả chi tiết môi trường phát triển chương trình C++
Pha 1: Xây dựng chương trình
(13)dành
cho
hội
đồng
nghiệm
thu
1.5 Lịch sử C C++ 5
biến hệ điều hành UNIX vim emacs Đối với hệ điều hành Window, gói phần mềm C++ Microsoft Microsoft Visual C++ có trình soạn thảo tích hợp vào mơi trường lập trình Chúng ta sử dụng trình soạn thảo đơn giản Notepad Window để viết chương trình mã nguồn C++
Pha 3: Tiền xử lý biên dịch chương trình C++
Trong bước này, thực lệnh dịch chương trình mã nguồn C++ Trong hệ thống C++, trình tiền xử lý thi hành tự động trước bước thực thi trình biên dịch Trình tiền xử lý thực thị tiền xử lý Các thao tác thực trước biên dịch chương trình Các thao tác thường bao gồm file văn khác biên dịch thực việc thay đoạn văn khác Chỉ thị tiền xử lý thông dụng đề cập chi tiết phần phụ lục Trong pha 3, trình biên dịch dịch chương trình C++ thành mã ngơn ngữ máy (mã đối tượng)
Pha 4: Liên kết
Chương trình C++ bao gồm tham chiếu tới hàm liệu định nghĩa nơi khác thư viện chuẩn thư viện người dùng tự tạo Mã đối tượng sinh trình biên dịch C++ thường chứa “lỗ” phần thiếu Trình liên kết liên kết mã đối tượng với mã hàm thiếu để tạo chương trình thi hành
Pha 5: Nạp
Trước chương trình thi hành, phải đặt nhớ Nó thực nhờ vào trình nạp cách lấy hình ảnh chương trình đĩa chuyển vào nhớ Các thành phần thêm từ thư viện chia nạp vào để hỗ trợ chạy chương trình
Pha 6: Thi hành
Cuối cùng, máy tính điều khiển CPU thi hành chương trình Vấn đề xuất thi hành chương trình Chương trình khơng phải ln làm việc lần chạy thử Mỗi pha trước thất bại lỗi khác mà thảo luận suốt giáo trình Ví dụ, chương trình thi hành cố gắng thực chia cho Điều nguyên nhân mà hệ thống thơng báo lỗi Khi đó, phải quay lại pha soạn thảo sửa lại lỗi chương trình thực lại pha
1.5 Lịch sử C C++
(14)dành
cho
hội
đồng
nghiệm
thu
6 Mở đầu
Hình 0.3: Mơi trường C++ để xây dựng chương trình
1.5 Lịch sử C C++
Ngôn ngữ C ++ phát triển từ ngôn ngữ C, C phát triển từ hai ngơn ngữ lập trình trước ngơn ngữ BCPL ngơn ngữ B BCPL phát triển vào năm
Hình 1.3: Các bước để xây dựng chương trình
Ngơn ngữ C phát triển từ B Dennis Ritchie Bell Laboratories C sử dụng nhiều khái niệm quan trọng BCPL B C ban đầu biết đến rộng rãi ngôn ngữ phát triển hệ điều hành UNIX Ngày nay, hầu hết hệ điều hành viết C/C++ C có sẵn cho hầu hết máy tính phần cứng độc lập
Ngôn ngữ C thiết kế phù hợp với máy tính C sử dụng rộng rãi với nhiều máy tính khác (các tảng phần cứng) dẫn đến nhiều biến thể Đây vấn đề nhà phát triển chương trình cần viết chương trình chạy nhiều tảng
Cần thiết có phiên tiêu chuẩn C Viện Tiêu chuẩn Quốc gia Hoa Kỳ (ANSI) phối hợp với Tổ chức Tiêu chuẩn Quốc tế (ISO) để chuẩn C toàn giới, tiêu chuẩn chung công bố vào năm 1990 gọi chuẩn ANSI/ISO 9899:1990
(15)dành
cho
hội
đồng
nghiệm
thu
1.6 Chương trình C++: In dòng văn bản 7
C để theo kịp phát triển mạnh phần cứng yêu cầu ngày cao người dùng C99 mang lại cho C nhiều thích hợp với C++ Để biết thêm thông tin C C99, tham khảo chi tiết sách [5] Do ngôn ngữ C ngôn ngữ chuẩn, độc lập phần cứng, ngôn ngữ phổ biến, ứng dụng viết C chạy với khơng có lỗi lỗi phạm vi rộng
Ngôn ngữ C++ mở rộng từ ngôn ngữ C, phát triển Bjarne Stroustrup vào đầu năm 1980 Bell Laboratories C++ cung cấp số tính cải tiến từ ngơn ngữ C, quan trọng hơn, cung cấp khả lập trình hướng đối tượng
Cuộc cách mạng diễn cộng đồng phần mềm Xây dựng phần mềm cách nhanh chóng, xác kinh tế mục tiêu khó, thời điểm nhu cầu phần mềm tốt tăng lên Các mục tiêu tái sử dụng thành phần phần mềm mơ hình giới thực Các nhà phát triển phần mềm phát mơ-đun, thiết kế hướng đối tượng có nhiều ưu điểm so với kỹ thuật lập trình cấu trúc phổ biến trước Các chương trình hướng đối tượng dễ hiểu, xác dễ sửa đổi
1.6 Chương trình C++: In dòng văn bản Phần này, xem xét chương trình đơn giản in hình dịng xâu ký tự hình
1.4 Đây chương trình minh họa đặc trưng quan trọng ngôn ngữ lập trình C++ Chúng ta xem xét chi tiết
1 // Text - printing program
3 # include <iostream >
5 // function main begins program execution int main ()
7 {
8 std :: cout << " Welcome to C++!\n"; // display message return 0; // indicate that program ended successfully 10 }
Hình 1.4: Chương trình C++
Output chương trình Hình 1.4:
Welcome to C++!
Mỗi dòng bắt đầu // , phần lại dòng thích Chúng ta thường chèn thích vào chương trình mã nguồn để giúp người khác đọc hiểu chúng Chú thích khơng có tác dụng chương trình chạy Chúng thường bỏ qua trình biên dịch C++ Bắt đầu thích với // gọi thích dịng đơn kết thúc vào cuối dịng thời Chúng ta sử dụng thích nhiều dịng bắt đầu với /* và kết thúc với */
(16)dành
cho
hội
đồng
nghiệm
thu
8 Mở đầu
Dòng đơn giản dòng trống Chúng ta sử dụng dòng trống, ký tự trắng, ký tự tab để làm cho chương trình dễ đọc Những ký tự gọi chung khoảng trắng Các ký tự khoảng trắng thường bỏ qua trình biên dịch
Dịng dịng thích đơn dẫn thi hành chương trình bắt đầu dòng Dòng (int main()) phần chương trình C++ Dấu ngoặc sau main ra rằng main hàm Chương trình C++ bao gồm nhiều hàm Chính xác, một hàm chương trình C++ phải tên main Hình1.4 bao gồm hàm Chương trình C++ bắt đầu thi hành hàm main main hàm chương trình Từ khóa int bên trái hàm main hàm main trả giá trị nguyên.
(17)dành
cho
hội
đồng
nghiệm
thu
1.6 Chương trình C++: In dòng văn bản 9
Bài tập
1 Hãy dùng dấu hoa thị * để vẽ tên hình Ví dụ
*** *** ************* ***
*** *** ************* ***
*** *** *** ***
*** *** *** ***
*** *** *** ***********
***** *** ***********
2 Hãy gõ lại biên dịch chương trình sau thành file chạy hello (hoặc hello.exe Windows) Chương trình nhận tên người từ dòng lệnh in câu chào "Hello, <tên người>"
# include <iostream >
using namespace std;
int main(int argc , char** argv) {
cout << "Hello , " << argv [1] << endl;
return 0; }
Sau biên dịch chương trình trên, để chạy nó, bạn cần mở cửa sổ dịng lệnh. • Trong Windows: nhấn phím cửa sổ + phím R gõ cmd ấn Enter • Trong Linux: chạy chương trình Terminal.
Trong cửa sổ dịng lệnh, bạn di chuyển đến thư mục chứa file chạy vừa biên dịch lệnh cd Sau đó, bạn chạy chương trình lệnh
./ hello Vinh (hoặc hello.exe Vinh Windows)
(18)dành
cho
hội
đồng
nghiệm
(19)dành
cho
hội
đồng
nghiệm
thu
Chương 2
Một số khái niệm C++
Trong chương này, tập trung tìm hiểu khái niệm C++ khai báo thao tác biến, kiểu liệu, biểu thức, … thơng qua số chương trình C++ Từ cho phép bạn xây dựng chương trình viết ngơn ngữ lập trình C++
2.1 Khai báo biến sử dụng biến
Dữ liệu xử lý dùng chương trình gồm liệu số ký tự C++ hầu hết ngơn ngữ lập trình sử dụng cấu trúc biến để đặt tên lưu trữ liệu Biến thành phần trung tâm ngôn ngữ lập trình C++ Bên cạnh việc chương trình phải có cấu trúc rõ ràng, số đặc điểm đưa để giải thích
2.1.1 Biến
Một biến ngơn ngữ C ++ lưu trữ số liệu thuộc kiểu khác Ta tập trung vào biến dạng số Các biến viết thay đổi
1 // Chuong trinh minh hoa # include <iostream > using namespace std; int main( )
5 {
6 int number_of_bars ;
7 double one_weight , total_weight ;
9 cout << " Enter the number of candy bars in a package \n"; 10 cout << "and the weight in ounces of one candy bar \n"; 11 cout << "Then press return \n";
12 cin >> number_of_bars ; 13 cin >> one_weight ; 14
15 total_weight = one_weight * number_of_bars ; 16
17 cout << number_of_bars << " candy bars\n"; 18 cout << one_weight << " ounces each\n";
(20)dành
cho
hội
đồng
nghiệm
thu
12 Một số khái niệm C++
21 cout << "Try another brand \n";
22 cout << " Enter the number of candy bars in a package \n"; 23 cout << "and the weight in ounces of one candy bar \n"; 24 cout << "Then press return \n";
25 cin >> number_of_bars ; 26 cin >> one_weight ; 27
28 total_weight = one_weight * number_of_bars ; 29
30 cout << number_of_bars << " candy bars\n"; 31 cout << one_weight << " ounces each\n";
32 cout << " Total weight is " << total_weight << " ounces \n"; 33
34 cout << " Perhaps an apple would be healthier \n"; 35
36 return 0; 37 }
Hình 2.1: Chương trình minh họa thao tác với biến C++
Trong ví dụ 2.1, number_of_bars, one_weight, total_weight biến Chương trình chạy với đầu vào thể đối thoại mẫu, number_of_bars thiết lập giá trị 11 câu lệnh
cin >> number_of_bars ;
giá trị biến number_of_bars thay đổi đến 12 câu lệnh chép thứ hai thực Trong ngôn ngữ lập trình, biến thực địa nhớ Trình biên dịch gán địa nhớ (đề cập Chương 1) cho tên biến chương trình Các giá trị biến, hình thức mã hóa bao gồm bit 1, lưu trữ theo địa nhớ gán cho biến Ví dụ, ba biến ví dụ hình 2.1 gán địa nhớ 1001, 1003, 1007 Các số xác phụ thuộc vào máy tính, trình biên dịch yếu tố khác Trình biên dịch lựa chọn giá trị cho biến chương trình, biểu diễn địa nhớ gán qua tên biến
2.1.2 Tên hay định danh
Điều bạn nhận thấy tên biến ví dụ dài tên thường dùng lớp tốn học Để làm cho chương trình dễ hiểu, nên sử dụng tên có ý nghĩa cho biến Tên biến (hoặc đối tượng khác xác định chương trình) gọi định danh
Một định danh phải bắt đầu chữ dấu _, tất phần lại chữ cái, chữ số, dấu _ Ví dụ, định danh sau hợp lệ:
x x1 x_1 _abc ABC123z7 sum RATE count data2 Big_Bonus
Tất tên đề cập trước hợp lệ trình biên dịch chấp nhận, năm tên định danh khơng phải mơ tả sử dụng định danh Những định danh sau khơng hợp lệ khơng trình biên dịch chấp nhận:
(21)dành
cho
hội
đồng
nghiệm
thu
2.1 Khai báo biến sử dụng biến 13
data -1 myfirst c PROG.CPP
Ba định danh đầu không phép khơng bắt đầu chữ dấu _ Ba định danh lại chứa ký hiệu khác với chữ cái, chữ số dấu _
C++ ngơn ngữ lập trình chặt chẽ phân biệt chữ hoa chữ thường Do ba định danh sau riêng biệt sử dụng để đặt tên cho ba biến khác nhau:
rate RATE Rate
Tuy nhiên, ý tưởng tốt để sử dụng chương trình gây khó hiểu Mặc dù khơng phải yêu cầu C++, biến thường viết với chữ thường Các định danh định nghĩa trước như: main, cin, cout, … phải viết chữ thường
Một định danh C++ có chiều dài tùy ý, số trình biên dịch bỏ qua tất ký tự sau số quy tắc số lượng lớn ký tự khởi tạo ban đầu
Có lớp đặc biệt định danh, gọi từ khoá định nghĩa sẵn C++ sử dụng để đặt tên cho biến dùng vào công việc khác Các từ khóa viết theo cách khác như: int, double Danh sách từ khóa đưa Phụ lục
Bạn tự hỏi từ khác, chúng định nghĩa phần ngôn ngữ C++ lại từ khóa Những từ cin cout? Câu trả lời bạn phép xác định lại từ này, khó hiểu để làm Những từ xác định trước khơng phải từ khóa Tuy nhiên, chúng định nghĩa thư viện theo yêu cầu tiêu chuẩn ngôn ngữ C++
Chúng thảo luận thư viện sau sách Để bây giờ, bạn không cần phải lo lắng thư viện Khơng cần phải nói, việc dùng định danh xác định trước cho điều khác ý nghĩa tiêu chuẩn gây nhầm lẫn nguy hiểm, nên tránh
Khai báo biến
Mỗi biến chương trình C ++ phải khai báo Khi bạn khai báo biến nghĩa cho trình biên dịch biết máy tính hiểu loại liệu bạn lưu trữ biến Ví dụ, hai khai báo sau ví dụ 2.1 khai báo biến sử dụng chương trình:
int number_of_bars ;
double one_weight , total_weight ;
Khi có nhiều biến khai báo, biến cách dấu phẩy Khai báo kết thúc dấu chấm phẩy
Từ int dòng đầu khai báo số nguyên Khai báo number_of_bars biến kiểu int Giá trị number_of_bars phải số nguyên, 1, 2, -1, 0, 37, -288
Từ double dòng thứ hai khai báo one_weight total_weight biến kiểu double Biến kiểu double lưu giữ số với phần lẻ sau dấu thập phân (số dấu chấm động), 1,75 -0,55 Các loại liệu tổ chức biến gọi kiểu tên kiểu, int double, gọi tên kiểu
Mỗi biến chương trình C++ phải khai báo trước sử dụng Có hai cách để khai báo biến: trước sử dụng sau bắt đầu hàm main chương trình
(22)dành
cho
hội
đồng
nghiệm
thu
14 Một số khái niệm C++
Điều làm cho chương trình rõ ràng Khai báo biến
Tất biến phải khai báo trước sử dụng Cú pháp để khai báo biến sau:
Type_name Variable_Name_1, Variable_Name_2, ; Ví dụ:
int count, number_of_dragons, number_of_trolls; double distance;
Khai báo biến cung cấp thơng tin cho trình biên dịch để biết thể biến Trình biên dịch thể biến nhớ địa phương giá trị biến gán cho biến Các giá trị mã hoá bit Các kiểu khác biến yêu cầu kích thước nhớ khác phương pháp khác để mã hóa giá trị bit Việc khai báo biến cho phép trình biên dịch phân bổ vị trí nhớ, kích thước nhớ cho biến để sử dụng chương trình
2.1.3 Câu lệnh gán
Cách trực tiếp để thay đổi giá trị biến sử dụng câu lệnh gán Một câu lệnh gán thứ tự để máy tính biết, “thiết lập giá trị biến với viết ra” Các dịng sau chương trình2.1 ví dụ câu lệnh gán
total_weight = one_weight * number_of_bars ;
Khai báo thiết lập giá trị total_weight tích one_weight number_of_bars Một câu lệnh gán ln bao gồm biến phía bên trái dấu biểu thức bên tay phải Câu lệnh gán kết thúc dấu chấm phẩy Phía bên phải dấu biến, số, biểu thức phức tạp biến, số, toán tử số học * + Một lệnh gán thị máy tính tính giá trị biểu thức bên phải dấu thiết lập giá trị biến phía bên trái dấu với giá trị tính
Có thể sử dụng tốn tử số học để thay phép nhân Ví dụ, câu lệnh gán giá trị:
total_weight = one_weight + number_of_bars ;
Câu lệnh giống câu lệnh gán ví dụ mẫu, ngoại trừ việc thực phép cộng nhân Khai báo thay đổi giá trị total_weight tổng giá trị one_weight number_of_bars Nếu thực thay đổi chương trình hình 2.1, chương trình cho giá trị khơng với mục đích, chạy
Trong câu lệnh gán, biểu thức bên phải dấu đơn giản biến Khai báo:
total_weight = one_weight ;
thay đổi giá trị total_weight giống giá trị biến one_weight
Nếu sử dụng chương trình hình 2.1, cho giá trị khơng xác thấp giá trị total_weight
(23)dành
cho
hội
đồng
nghiệm
thu
2.2 Vào liệu 15
number_of_bars = 37;
Số 37 ví dụ gọi số, khơng giống biến, giá trị khơng thể thay đổi Các biến thay đổi giá trị phép gán cách để thay đổi Trước hết, biểu thức bên phải dấu tính tốn, sau giá trị biến bên trái gán giá trị tính tốn bên phải Nghĩa là, biến hai bên tốn tử gán Ví dụ, xét câu lệnh gán:
number_of_bars = number_of_bars + 3;
Giá trị thực “Giá trị number_of_bars với giá trị number_of_bars cộng với ba” hay “Giá trị number_of_bars với giá trị cũ number_of_bars cộng với ba” Dấu C++ không sử dụng theo nghĩa dấu ngôn ngữ thông thường theo nghĩa đơn giản toán học
Câu lệnh gán
Trong khai báo, biểu thức bên phải dấu tính tốn, sau biến bên trái dấu thiết lập với giá trị
Cú pháp
Biến = biểu thức; Ví dụ
distance = rate * time; count = count + 2; 2.2 Vào liệu
Đối với chương trình C++ có nhiều cách để nhập xuất liệu Ở đây, mô tả cách gọi luồng (stream) Một luồng nhập (input stream) hiểu đơn giản dòng liệu đưa vào máy tính để sử dụng Luồng cho phép chương trình xử lý liệu đầu vào theo cách nhau, chúng nhập vào hình thức Luồng tập trung vào dịng liệu mà khơng quan tâm đến nguồn gốc liệu.Trong phần này, giả định liệu nhập vào bàn phím xuất hình Trong chương 8, tìm hiểu thêm xuất nhập liệu từ tệp tin
2.2.1 Xuất liệu với cout
cout cho phép xuất hình giá trị biến chuỗi văn Có nhiều kết hợp biến chuỗi văn để xuất Ví dụ: xem câu lệnh chương trình phần 2.1
cout << number_of_bars << " candy bars\n";
Câu lệnh cho phép máy tính xuất hình hai mục: giá trị biến number_of_bars cụm từ trích dẫn "candy bars\n" Lưu ý rằng, bạn không cần thiết phải lặp lại câu lệnh cout cho lần xuất liệu Bạn cần liệt kê tất liệu đầu với biểu tượng mũi tên << phía trước Câu lệnh cout tương đương với hai câu lệnh cout đây:
(24)dành
cho
hội
đồng
nghiệm
thu
16 Một số khái niệm C++
Bạn đưa cơng thức tốn học vào câu lệnh cout thể ví dụ đây, price tax biến
cout << "The total cost is $" << ( price + tax );
Đối với biểu thức toán học price + tax trình biên dịch u cầu phải có dấu ngoặc đơn Hai biểu tượng < đánh sát khơng có dấu cách gọi tốn tử chèn Toàn câu lệnh cout kết thúc dấu chấm phẩy
Nếu có hai lệnh cout dịng, bạn kết hợp chúng lại thành lệnh cout dài Ví dụ, xem xét dịng sau từ hình2.1
cout << number_of_bars << " candy bars\n"; cout << one_weight << " ounces each\n";
Hai câu lệnh viết lại thành câu lệnh đơn chương trình thực xác câu lệnh cũ
cout << number_of_bars << " candy bars\n" << one_weight << " ounces each\n";
Bạn nên tách câu lệnh thành hai nhiều dịng thay câu lệnh dài để giữ cho câu lệnh khơng bị chạy khỏi hình
cout << number_of_bars << " candy bars\n"
<< one_weight << " ounces each\n";
Bạn không cần phải cắt ngang chuỗi trích dẫn thành hai dịng, mặt khác, bạn bắt đầu dòng bạn chỗ trống Những khoảng trống ngắt dòng hợp lý máy tính chấp nhận ví dụ
Bạn nên sử dụng lệnh cout cho nhóm liệu đầu Chú ý có dấu chấm phẩy cho lệnh cout, với lệnh kéo dài
Từ ví dụ đầu hình 2.1, cần ý chuỗi trích dẫn phải có ngoặc kép Đây ký tự ngoặc kép bàn phím, khơng sử dụng hai ngoặc đơn để tạo thành ngoặc kép Bên cạnh đó, cần ý ngoặc kép sử dụng để kết thúc chuỗi Đồng thời, khơng có phân biệt ngoặc trái ngoặc phải
Cũng cần ý đến khoảng cách bên chuỗi trích dẫn Máy tính khơng chèn thêm khoảng cách trước sau dòng liệu câu lệnh cout Vì vậy, chuỗi trích dẫn mẫu thường bắt đầu và/hoặc kết thúc với dấu cách Dấu cách giữ cho chuỗi ký tự số xuất Nếu bạn muốn có khoảng trống mà chuỗi trích dẫn khơng có bạn đặt thêm vào chuỗi có khoảng trống ví dụ đây:
cout << first_number << " " << second_number ;
(25)dành
cho
hội
đồng
nghiệm
thu
2.2 Vào liệu 17
2.2.2 Chỉ thị biên dịch không gian tên
Chúng ta bắt đầu chương trình với dịng sau đây:
# include <iostream >
using namespace std;
Hai dòng cho phép người lập trình sử dụng thư viện iostream Thư viện bao gồm định danh cin cout nhiều định danh khác Bởi vậy, chương trình bạn sử dụng cin và/hoặc cout, bạn nên thêm dòng bắt đầu tệp chứa chương trình bạn
Dòng xem “chỉ thị bao gồm” Nó “bao gồm” thư viện iostream chương trình bạn, người sử dụng dùng cin cout:
# include <iostream >
Toán tử cin cout định danh tệp iostream dịng phía tương đương với việc chép tập tin chứa định danh vào chương trình bạn Dịng thứ hai tương đối phức tạp để giải thích
C++ chia định danh vào “không gian tên (namespace)” Không gian tên tập hợp chưa nhiều định danh, ví dụ cin cout Câu lệnh định không gian tên ví dụ gọi sử dụng thị
using namespace std;
Việc sử dụng thị cụ thể cho biết chương trình bạn sử dụng không gian tên std (không gian tên chuẩn) Tức định danh mà bạn sử dụng nhận diện không gian tên std Trong trường hợp này, điều quan trọng đối tượng cin cout định danh iostream, định danh chúng cho biết chúng nằm khơng gian tên std Vì để sử dụng chúng, bạn cần báo với trình biên dịch bạn sử dụng không gian tên std
Lý C++ có nhiều khơng gian tên có nhiều đối tượng cần phải đặt tên Do đó, đơi có hai nhiều đối tượng có tên gọi, điều cho thấy có hai định danh khác cho tên gọi Để giải vấn đề này, C++ phân chia liệu thành tuyển tập, nhờ loại bỏ việc hai đối tượng tuyển tập (không gian tên) bị trùng lặp tên
Chú ý rằng, không gian tên không đơn giản tuyển tập định danh Nó phần thân chương trình C++ nhằm xác định ý nghĩa số đối tượng, ví dụ số định danh hoặc/và khai báo Chức không gian tên chia tất định danh C++ thành nhiều tuyển tập, từ đó, định danh có nhận dạng khơng gian tên
Một số phiên C++ sử dụng dẫn Đây phiên cũ “chỉ dẫn bao gồm” (không sử dụng không gian tên)
# include <iostream h>
Nếu trình biên dịch bạn khơng chạy với dịng dẫn:
# include <iostream >
using namespace std;
thì thử sử dụng dòng dẫn để thay thế:
(26)dành
cho
hội
đồng
nghiệm
thu
18 Một số khái niệm C++
Nếu trình biên dịch bạn yêu cầu iostream.h thay iostream, bạn sử dụng trình biên dịch phiên cũ bạn nên có trình biên dịch phiên
2.2.3 Các chuỗi Escape
Có nhiều kí tự dùng cho nhiệm vụ đặc biệt dấu ' (cho biểu diễn kí tự), dấu " (cho biểu diễn xâu) Các kí tự xuất số trường hợp gây lỗi, ví dụ để gán biến letter kí tự ' (single quote) ta khơng thể viết: letter = '''; dấu nháy đơn được hiểu kí hiệu bao lấy kí tự Tương tự câu lệnh: cout << "This is double quote (")"; sai Để biểu diễn kí tự (cũng kí tự điều khiển khơng có mặt chữ, như kí tự xuống dịng) ta dùng chế “thốt” cách thêm kí hiệu \ vào phía trước Các dấu gạch chéo ngược, \ , viết liền trước ký tự cho biết ký tự khơng có ý nghĩa giống thơng thường Như vậy, câu lệnh cần viết lại:
letter = '\'';
cout << "This is double quote (\")";
Và đến lượt mình, dấu \ trưng dụng để làm nhiệm vụ đặc biệt trên, nên để biểu thị \ ta cần phải viết \\.
Chuỗi gồm dấu \ liền kí tự bất kỳ, gọi chuỗi Sau \ một kí tự bất kỳ, kí tự chưa qui định ý nghĩa theo tiêu chuẩn ANSI hành vi chuỗi khơng xác định Từ đó, số trình biên dịch đơn giản bỏ qua dấu \ xem kí tự với ý nghĩa gốc, cịn số khác ”hiểu nhầm” gây hiệu ứng khơng tốt Vì vậy, bạn nên sử dụng chuỗi cung cấp Chúng tơi liệt kê số chuỗi
Thuật ngữ Ký hiệu Ý nghĩa
new line \n xuống dòng
horizontal tab \t dịch chuyển trỏ số dấu cách
alert \a tiếng chuông
backslash \\ dấu \
single quote \' dấu '
double quote \" dấu "
2.2.4 Nhập liệu với cin
Bạn sử dụng cin để nhập liệu nhiều tương tự cách mà bạn sử dụng cout để xuất liệu Cú pháp tương tự, trừ việc cin thay cho cout << thay >> Chương trình hình2.1, biến number_of_bars one_weight nhập vào với lệnh cin sau:
cin >> number_of_bars ; cin >> one_weight ;
cũng tương tự cout, bạn gộp hai dịng lệnh thành viết dòng:
cin >> number_of_bars >> one_weight ;
(27)dành
cho
hội
đồng
nghiệm
thu
2.2 Vào liệu 19
cin >> number_of_bars >> one_weight ;
Và ý với cin có dấu chấm phẩy Cách nhập liệu với >>
Khi gặp câu lệnh cin chương trình chờ bạn nhập dãy giá trị vào từ bàn phím đặt giá trị biến thứ với giá trị thứ nhất, biến thứ hai với giá trị thứ hai … Tuy nhiên, sau bạn nhấn Enter chương trình nhận lấy dịng liệu nhập phân bố giá trị cho biến Điều có nghĩa bạn nhập tất giá trị cho biến (trong nhiều câu lệnh cin >>) lần với dấu Enter), điều tạo thuận lợi cho NSD kịp thời sửa chữa, xóa, bổ sung dịng liệu nhập (nếu có sai sót) trước nhấn Enter Các giá trị nhập cho biến phải cách dấu trắng (là dấu cách, dấu tab chí dấu xuống dịng – enter) Ví dụ cần nhập giá trị 12 cho biến number_of_bars one_weight thông qua câu lệnh:
cin >> number_of_bars >> one_weight ;
Có thể nhập 12 Enter
12 Enter
5 Enter
Chương trình bỏ qua dấu Space , dấu Tab , dấu Enter gán 12 cho number_of_bars
5 cho one_weight
Vì chương trình bỏ qua khơng gán dấu trắng cho biến (kể biến xâu kí tự) nên giả sử candy_mark xâu kí tự ta có câu lệnh:
cin >> number_of_bars >> one_weight >> candy_mark ;
và dòng nhập: 12 peanut candy Enter
thì biến candy_mark nhận giá trị: "peanut" thay "peanut candy" Để xâu nhận đầy đủ thông tin nhập ta cần lệnh nhập khác xâu (xem chương 5)
Khi NSD nhập vào dãy byte nhiều cần thiết để gán cho biến số byte cịn lại kể dấu xuống dịng (nhập phím Enter ) nằm lại cin Các byte tự động gán cho biến lần nhập sau mà không chờ NSD gõ thêm liệu vào từ bàn phím Ví dụ:
# include <iostream >
using namespace std;
int main( ) {
char my_name ;
int my_age ;
cout << " Enter data: ";
cin >> my_name >> my_age ; // Gia su nhap A 15 B 16
cout << "My name is " << my_name ;
cout << " and I am " << my_age << " years old \n";
char your_name ;
int your_age ;
cout << " Enter data: ";
cin >> your_name >> your_age ;
(28)dành
cho
hội
đồng
nghiệm
thu
20 Một số khái niệm C++
cout << " and you is " << your_age << " years old \n";
return 0; }
Chương trình gồm hai đoạn lệnh giống nhau, nhập tên, tuổi in hình cho nhân vật tơi, đoạn lại thực giống hệt cho nhân vật bạn Giả sử đáp ứng lệnh nhập đầu tiên, NSD nhập: A 15 B 16 chương trình in ln kết hình mà không cần chờ nhập cho lệnh nhập thứ hai Dưới output chương trình
Enter data: A 15 B 16
My name is A and I am 15 years old.
Enter data: Your name is B and you is 16 years old.
Thông báo trước nhập liệu (kết hợp cout với cin)
Khi gặp lệnh nhập liệu chương trình đơn giản dừng lại chờ không tự động thơng báo hình, ta cần “nhắc nhở” NSD nhập liệu (số lượng, loại, kiểu … cho biến …) câu lệnh cout << kèm phía trước Ví dụ:
cout << " Enter the number of candy bars in a package \n"; cout << "and the weight in ounces of one candy bar \n"; cout << "Then press return \n";
cin >> number_of_bars >> one_weight ;
hoặc
cout << " Enter your name and age: "; cin >> your_name >> your_age ;
2.3 Kiểu liệu biểu thức
2.3.1 Kiểu int kiểu double
(29)dành
cho
hội
đồng
nghiệm
thu
2.3 Kiểu liệu biểu thức 21
Kiểu double gì?
Tại số có phần phập phân gọi double? Với kiểu liệu ”single” giá trị có nửa? Khơng, có số thứ gần giống Rất nhiều ngơn ngữ lập trình truyền thống sử dụng hai kiểu liệu cho số thập phân Một kiểu sử dụng lưu trữ tốn nhớ độ xác thấp (khơng cho phép sử dụng nhiều chữ số phần thập phân) Dạng thứ hai sử dụng gấp đôi dung lượng nhớ xác cho phép sử dụng số có giá trị lớn (mặc dù người lập trình quan tâm nhiều đến độ xác kích thước nhớ) Các số sử dụng gấp đơi kích thước nhớ gọi số có độ xác kép; số sử dụng nhớ gọi số có độ xác đơn Theo cách gọi số có độ xác kép gọi C++ số double Các số có độ xác đơn gọi số float C++ có số dạng thứ ba gọi long double, số mô tả phần ”Các kiểu liệu khác” Tuy nhiên, sử dụng kiểu float long double sách
Các giá trị số kiểu double viết khác với kiểu int Các giá trị kiểu int không chứa số thập phân Nhưng giá trị kiểu double cần viết phần nguyên phần thập phân (ví dụ 2.1, 2.0 ) Dạng thức viết đơn giản giá trị double giống viết số thực hàng ngày Khi viết dạng này, giá trị double phải chứa phần thập phân
Một cách viết phức tạp giá trị kiểu double gọi ký hiệu khoa học hay ký hiệu dấu phẩy động để viết cho số lớn bé Ví dụ:
3.67 x 1017 tương đương với
367000000000000000.0
và biểu diễn C++ giá trị 3.67e17 Với số 5.89 x 10-6
tương đương với 0.0000589
và biểu diễn C++ giá trị 5.89e-6 Chữ e viết tắt exponent có nghĩa số mũ lũy thừa 10
Ký hiệu e sử dụng phím bàn phím khơng thể biểu diễn số bên mũ Số sau chữ e cho ta hướng số chữ số cần dịch chuyển dấu thập phân Ví dụ, để thay đổi số 3.49e4 thành số không chứa ký tự e, ta di chuyển dấu thập phân sang bên phải chữ số ta 34900.0, cách viết khác số ban đầu Nếu số sau ký tự e số âm, ta di chuyển sang trái, thêm vào số cần thiết Do đó, số 3.49e-2 tương đương với 0.0349
Giá trị trước ký tự e chứa phần thập phân không Nhưng giá trị sau ký tự e bắt buộc giá trị không chứa phần thập phân
(30)dành
cho
hội
đồng
nghiệm
thu
22 Một số khái niệm C++
2.3.2 Các kiểu số khác
Trong C++ cịn có kiểu liệu số khác kiểu int double, kiểu liệu số trình bày bảng 2.2 Các kiểu liệu có miền giá trị số độ xác khác (tương ứng với nhiều số chữ số phần thập phân) Trong bảng 2.2, kiểu liệu mô tả kèm với kích thước nhớ, miền giá trị độ xác Các giá trị thay đổi hệ thống khác
Mặc dù có số kiểu liệu viết hai từ, khai báo biến thuộc kiểu giống với kiểu int double Ví dụ sau khai báo biến có kiểu long double:
long double big_number ;
Kiểu liệu long long int hai tên cho kiểu Do đó, hai khai báo sau tương đương:
long big_total ;
tương đương với
long int big_total ;
Trong chương trình, bạn sử dụng hai kiểu khai báo cho biến big_total, chương trình khơng quan tâm bạn sử dụng kiểu liệu Do đó, kiểu liệu long tương đương với long int, không tương đương với long double
Các kiểu liệu cho số nguyên int kiểu tương tự gọi kiểu số nguyên Kiểu liệu cho số có phần thập phân kiểu double số kiểu tương tự gọi kiểu số thực (kiểu dấu phẩy động) Các kiểu gọi kiểu dấy phẩy động máy tính lưu số tương ứng với viết, ví dụ số 392.123, chuyển sang dạng ký hiệu e ta 3.92123e2 Khi máy tính thực biến đổi này, dấu phẩy động dịch chuyển sang vị trí
Chúng ta nên biết kiểu liệu số C++ Tuy nhiên, giáo trình này, chúng tơi sử dụng kiểu liệu int, double long Đối với ứng dụng đơn giản, không cần sử dụng kiểu liệu khác int double Nhưng bạn viết ứng dụng cần sử dụng đến số lớn dùng sang kiểu long
2.3.3 Kiểu C++11
Miền giá trị kiểu số ngun thay đổi máy tính có hệ điều hành khác Ví dụ, máy có hệ điều hành 32-bit số nguyên cần bytes để lưu trữ, máy có hệ điều hành 64-bit kiểu số nguyên cần bytes Điều dẫn đến nhiều vấn đề bạn không hiểu xác miền giá trị lưu trữ cho kiểu số nguyên Để giải vấn đề này, kiểu số nguyên thêm vào C++11 để rõ xác giá trị cho số có dấu số không dấu Để sử dụng kiểu liệu cần thêm <cstdint> khai báo Bảng2.3 biểu diễn số kiểu liệu
C++11 thêm vào kiểu có tên auto, chương trình tự suy kiểu liệu tương ứng dựa vào biểu thức toán học bên phải phép gán Ví dụ, dịng lệnh sau định nghĩa biến x có kiểu liệu tùy thuộc vào việc tính giá trị biểu thức từ “expression”:
(31)dành
cho
hội
đồng
nghiệm
thu
2.3 Kiểu liệu biểu thức 23
Bảng 2.2: Một số kiểu liệu số
Kiểu Kích thước Miền giá trị Độ xác
short (short int) bytes -32.768 đến 32.768
int bytes -2.147.483.648 đến 2.147.483.647 long (long int) bytes -2.147.483.648 đến 2.147.483.647
float bytes xấp xỉ từ 10-38 đến 1038 7 chữ số
double bytes xấp xỉ từ 10-308 đến 10308 15 chữ số
long double 10 bytes xấp xử từ 10-4932 đến 104932 19 chữ số Trong bảng đưa vài thông tin khác kiểu liệu số Các giá trị khác hệ thống khác Độ xác để số số phần thập phân Miền giá trị cho kiểu float, double long double miền giá trị cho số dương Đối với số âm miền giá trị tương tự chứa dấu âm phía trước số
Kiểu liệu không sử dụng nhiều thời điểm giúp cho tiết kiệm đoạn code sử dụng kiểu liệu lớn tự định nghĩa
Bảng 2.3: Một số kiểu số nguyên C++11 Kiểu Kích thước Miền giá trị int8_t bytes -27 đến 27-1 uint8_t bytes đến 28-1 int16_t bytes -215 đến 215-1 uint16_t bytes đến 216-1 int32_t bytes -231 đến 231-1 uint32_t bytes đến 232-1 int64_t bytes -263 đến 263-1 uint64_t bytes đến 264-1 long long Ít bytes
C++11 đưa cách thức để xác định kiểu biến biểu thức decltype(expr) dạng khai báo biến biểu thức:
int x = 10;
decltype (x*3.5) y;
(32)dành
cho
hội
đồng
nghiệm
thu
24 Một số khái niệm C++
2.3.4 Kiểu char
Trong máy tính C++, khơng sử dụng tính tốn với liệu số, xin giới thiệu số kiểu liệu phi số khác chí cịn phức tạp Các giá trị kiểu char viết tắt từ character kí tự đơn giống chữ cái, chữ số kí tự chấm câu Giá trị kiểu liệu gọi kí tự C++ gọi char Ví dụ, biến symbol letter có kiểu char khai báo sau:
char symbol , letter ;
Các biến có kiểu char chứa kí tự từ bàn phím Ví dụ, biến symbol lưu kí tự 'A' kí tự '+' Chú ý kí tự hoa kí tự thường hồn tồn khác
Đoạn văn nằm dấu hai nháy câu lệnh cout gọi xâu kí tự Ví dụ sau thực chương trình hình2.1 xâu kí tự:
" Enter the number of candy bars in a package \n"
Chú ý rằng, giá trị xâu kí tự đặt dấu nháy kép, kí tự thuộc kiểu char đặt dấu nháy đơn Hai dấu nháy có ý nghĩa hồn tồn khác Ví dụ, 'A' "A" hai giá trị khác 'A' giá trị biến kiểu char "A" xâu kí tự Mặc dù xâu kí tự chứa kí tự khơng thể biến xâu "A" có giá trị kiểu char Chú ý, với xâu kí tự kí tự, dấu nháy bên phải bên trái
Sử dụng kiểu char mô tả chương trình hình 2.4 Chú ý rằng, người dùng gõ khoảng trống hai giá trị nhập, chương trình bỏ qua khoảng trống nhập giá trị 'B' cho biến thứ hai Khi bạn sử dụng câu lệnh cin để đọc giá trị vào cho biến kiểu char, máy tính bỏ qua tất khoảng trống dấu xuống dòng gặp kí tự khác khoảng trống đọc kí tự vào biến Do đó, khơng có khác biệt giá trị chứa khoảng trống hay không chứa khoảng trống Chương trình hình 2.4 hiển thị giá trị hình với hai trường hợp người dùng gõ khoảng trống kí tự nhập vào trường hợp không chứa khoảng trống
1 // Chuong trinh minh hoa kieu char # include <iostream >
3 using namespace std; int main( )
5 {
6 char symbol1 , symbol2 , symbol3 ;
8 cout << " Enter two initials , without any periods :\n"; cin >> symbol1 >> symbol2 ;
10 cout << "The two initials are :\n"; 11 cout << symbol1 << symbol2 << endl; 12 cout << "Once more with a space :\n"; 13 symbol3 = ' ';
14 cout << symbol1 << symbol3 << symbol2 << endl; 15 cout << "That 's all.";
16 return 0; 17 }
(33)dành
cho
hội
đồng
nghiệm
thu
2.3 Kiểu liệu biểu thức 25
Kiểu bool Kiểu liệu đề cập kiểu bool Kiểu liệu ISO/ANSI (International Standards Organization/American National Standards Organization) đưa vào ngôn ngữ C++ năm 1998 Các biểu thức kiểu bool gọi Boolean nhà toán học người Anh Geogre Boole (1815-1864) đưa luật cho tốn học logic Các biểu thức boolean có hai giá trị (true) sai (false) Các biểu thức boolean sử dụng câu lệnh rẽ nhánh câu lệnh lặp, đề cập phần 2.4
2.3.5 Tương thích kiểu liệu
Theo quy tắc thông thường, bạn lưu giá trị thuộc kiểu liệu cho biến thuộc kiểu liệu khác Ví dụ, phần lớn trình biên dịch không cho phép sau:
int int_variable ; int_variable = 2.99;
Vấn đề không tương thích kiểu liệu Giá trị 2.99 kiểu double biến int_variable kiểu int Tuy nhiên, trình biên dịch xử lý vấn đề Một số trình biên dịch trả thông báo lỗi, số đưa cảnh báo, số không chấp nhận số kiểu Nhưng với trình biên dịch cho phép bạn sử dụng phép gán trên, biến int_variable nhận giá trị 2, Khi bạn khơng biết trình biên dịch có chấp nhận phép gán hay không, tốt bạn không nên gán giá trị số thực cho biến kiểu nguyên
Vấn đề tương tự bạn gán giá trị biến kiểu double thay cho giá trị 2.99 Phần lớn trình biên dịch khơng chấp nhận phép gán sau:
int int_variable ;
double double_variable ; double_variable = 2.00;
int_variable = double_variable ;
Thực tế giá trị 2.00 khơng có khác biệt Giá trị 2.00 kiểu double, kiểu int Như thấy, thay giá trị 2.00 phép gán giá trị cho biến double_variable, không đủ phép gán dòng thứ chấp nhận Các biến int_variable biến double_variable thuộc kiểu liệu khác nhau, nguyên nhân vấn đề
Mặc dù trình biên dịch cho phép sử dụng nhiều kiểu liệu phép gán, phần lớn trường hợp khơng nên sử dụng Ví dụ, trình biên dịch cho phép gán giá trị 2.99 cho biến kiểu nguyên, biến nhận giá trị thay 2.99 Vì thế, dễ bị hiểu nhầm chương trình nhận giá trị 2.99
Trong số trường hợp, giá trị biến gán cho giá trị biến Biến kiểu int gán giá trị cho biến kiểu double Ví dụ, câu lệnh sau hợp lệ:
double double_variable ; double_variable = 2;
Đoạn lệnh thực gán cho biến double_variable giá trị 2.0
(34)dành
cho
hội
đồng
nghiệm
thu
26 Một số khái niệm C++
tính tốn biến kiểu char tiết kiệm nhớ Tuy nhiên, nên sử dụng kiểu int làm việc với số nguyên sử dụng kiểu char làm việc với kí tự
Quy tắc bạn thay giá trị kiểu giá trị biến có kiểu khác, có nhiều trường hợp ngoại lệ trường hợp thực theo quy tắc Thậm chí trường hợp trình biên dịch khơng quy định chặt chẽ, theo quy tắc tốt Thay giá trị biến giá trị biến có kiểu liệu khác gây vấn đề giá trị bị thay đổi để với kiểu biến, làm cho giá trị cuối biến không mong muốn
2.3.6 Toán từ số học biểu thức
Trong chương trình C++, bạn kết hợp biến và/hoặc số sử dụng toán tử + cho phép cộng, - cho phép trừ, * cho phép nhân / cho phép chia Ví dụ, phép gán chương trình hình 2.1 sử dụng toán tử * để nhân số nằm hai biến (kết gán lại cho biến nằm bên trái dấu bằng)
total_weight = one_weight * number_of_bars ;
Tất tốn tử số học sử dụng cho số kiểu int, kiểu double kiểu số khác Tuy nhiên, giá trị kiểu liệu tính tốn giá trị xác phụ thuộc vào kiểu số hạng Nếu tất tốn hạng thuộc kiểu int, kết cuối kiểu int Nếu tất tốn hạng thuộc kiểu double, kết cuối double Ví dụ, biến base_amount increase có kiểu int, biểu thức sau có kiểu int:
base_amount + increase
Tuy nhiên, hai biến kiểu double kết trả kiểu double Tương tự với toán tử -, * /
Kiểu liệu kết phép tính xác bạn nghi ngờ Ví dụ, 7.0/2 có tốn hạng kiểu double, 7.0 Khi đó, kết kiểu double với giá trị 3.5 Tuy nhiên, 7/2 có hai tốn hạng kiểu int kết có kiểu int với giá trị Nếu kết chẵn có khác Ví dụ, 6.0/2 có tốn hạng kiểu double, tốn hạng 6.0 Khi đó, kết có kiểu double có giá trị 3.0 số xấp xỉ Tuy nhiên, 6/2 có hai tốn hạng kiểu int, kết trả thuộc kiểu int số xác Toán tử chia toán tử bị ảnh hưởng kiểu đối số
Khi sử dụng hai toán hạng kiểu double, phép chia / cho kết bạn tính Tuy nhiên, sử dụng với toán hạng kiểu int, phép chia / trả phần nguyên phép chia Hay nói cách khác, phép chia số nguyên bỏ qua phần thập phân Do đó, 10/3 cho kết (khơng phải 3.3333), 5/2 (không phải 2.5) 11/3 (không phải 3.66666) Chú ý số không làm tròn, phần thập phân bị bỏ qua với giá trị lớn hay nhỏ
Toán tử % sử dụng với toán hạng kiểu int để lấy lại phần giá trị bị sử dụng phép chia / với số nguyên Ví dụ, 17 chia dư Toán tử / trả thương Tốn tử % trả phần dư Ví dụ, câu lệnh sau:
cout << "17 divided by is " << (17/5) << endl; cout << "with a remainder of " << (17%5) << endl;
cho kết quả:
(35)dành
cho
hội
đồng
nghiệm
thu
2.3 Kiểu liệu biểu thức 27
with a remainder of 2
Khi sử dụng với số âm thuộc kiểu int, kết phép chia / phép lấy dư % khác trình biên dịch C++ khác Do đó, bạn nên sử dụng / % với giá trị nguyên bạn biết hai giá trị không âm
Các biểu thức tốn học có khoảng trống Bạn thêm khoảng trống trước sau toán tử dấu ngoặc đơn bỏ qua Viết theo cách mà ta dễ dàng đọc Chúng ta đưa thứ tự thực phép tốn cách sử dụng dấu ngoặc đơn mô tả đây:
(x + y) * z x + (y * z)
Mặc dù bạn sử dụng cơng thức tốn học có dấu ngoặc vuông số dấu ngoặc khác, dấu ngoặc khơng sử dụng C++ C++ cho phép dấu ngoặc đơn biểu thức toán học
Nếu bỏ qua dấu ngoặc đơn, máy tính thực tính tốn theo thứ tự ưu tiên thứ tự phép toán + * Thứ tự ưu tiên tương tự với đại số tốn học Ví dụ, phép nhân thực trước sau thực phép cộng Ngoại trừ số trường hợp, cộng xâu kí tự phép nhân bên phép cộng, với cách nên dùng thêm dấu ngoặc đơn Dấu ngoặc đơn thêm vào để biểu thức toán học dễ hiểu để tránh lỗi lập trình Bảng thứ tự ưu tiên mô tả phụ lục B
Khi bạn sử dụng phép chia / cho hai số nguyên, kết số nguyên Sẽ có vấn đề bạn mong muốn kết số thực Hơn nữa, vấn đề lại khó phát hiện, kết chương trình trơng chấp nhận tính tốn cho kết khơng Ví dụ, giả sử bạn kiến trúc sư cầu đường trả 5000$ dặm đường quốc lộ, giả sử bạn biết chiều dài đường đo feet Giá bạn đổi tính sau:
total_price = 5000 * (feet /5280.0);
Phép tốn thực 5280 feet dặm Nếu chiều dài đường quốc lộ bạn thi công 15000 feet, công thức trả cho bạn tổng giá trị
5000 * (15000/5280.0)
Chương trình C++ bạn nhận giá trị cuối sau: 15000/5280.0 2.84 Sau chương trình nhân với 5000 với 2.84 giá trị 14200.00 Sử dụng chương trình C++ đó, bạn biết phải trả 14.200$ cho dự án
Bây giả sử biết feet kiểu số nguyên, bạn quên không viết thêm dấu chấm số vào sau 5280, câu lệnh gán viết sau:
total_price = 5000 * (feet /5280);
Câu lệnh dường khơng có sai có vài vấn đề thực thi Nếu bạn sử dụng phép gán thứ hai, bạn chia hai giá trị kiểu int, kết phép chia feet/5280 tương đương với 15000/2580 giá trị (thay giá trị 2.84 lúc trước) Do giá trị gán cho biến total_cost 5000*2, 10000.00 Nếu bạn quên dấu thập phân bạn trả 10.000$
(36)dành
cho
hội
đồng
nghiệm
thu
28 Một số khái niệm C++
2.4 Luồng điều khiển
Các chương trình phần mềm tập hợp thống câu lệnh đơn giản hệ điều hành thực thi theo thứ tự Tuy nhiên, để viết phần mềm phức tạp, bạn cần thực nhiều câu lệnh với thứ tự phức tạp Để làm điều đó, bạn cần sử dụng cấu trúc rẽ nhánh cấu trúc điều khiển Trong phần này, xem xét chúng, cấu trúc điều khiển đơn giản cấu trúc if-else while (do-while)
Trên thực tế, việc bạn phải đưa lựa chọn việc tránh khỏi, điều khơng phải ngoại lệ lập trình Bạn phải đưa định lựa chọn câu lệnh hệ điều hành thực thi, bạn làm để thực điều đó? C++ cung cấp cho nhiều cách để làm điều đó, số chúng sử dụng cấu trúc rẽ nhánh if-else Sử dụng cấu trúc cho phép bạn lựa chọn thực một nhóm câu lệnh dựa điều kiện có sẵn
Ví dụ, giả sử bạn ông chủ bạn muốn viết chương trình để tính lương tuần theo cho nhân viên Cơng ty trả gấp rưỡi tiền lương cho làm thêm, số làm thêm tính số làm việc sau số làm việc bắt buộc (40 làm việc bắt buộc tuần) Thơng thường, bạn dễ dàng tính số tiền bạn trả cho nhân viên bạn sau:
Gross_pay = rate * 40 + 1.5* rate *( hours -40)
Tuy nhiên, bạn nhận vấn đề nhân viên bạn làm việc 40 tuần, sử dụng cơng thức có vấn đề xảy Trong trường hợp này, bạn phải sử dụng công thức khác để tính, là:
Gross_pay = rate *hours
Cơng ty bạn hiển nhiên có nhiều nhân viên, hai trường hợp xảy khơng thể tránh khỏi, bạn cần sử dụng hai công thức trên, nhiên vấn đề nằm chỗ, làm bạn biết bạn cần sử dụng công thức đầu tiền, bạn cần sử dụng công thức thứ hai chương trình bạn? C++ cung cấp cho bạn cấu trúc if-else để làm điều này, việc đơn giản mà bạn cần làm đặt chúng vào vị trí nó, việc đưa định sử dụng cấu trúc if-else đưa sau:
if(hours > 40) {
Gross_pay =rate * 40 + 1.5* rate *( hours -40) }
else{
Gross_pay =rate *hours }
Như vậy, bạn tính tốn xác số tiền phải trả cho nhân viên cho dù làm việc hay nhiều 40 tuần Cấu trúc if-else cấu trúc rẽ nhánh đơn giản Tuy nhiên, mang lại hiệu tốt việc đưa định dựa điều kiện đó, cú pháp sau:
if ( Boolean_Expression ) {
(37)dành
cho
hội
đồng
nghiệm
thu
2.4 Luồng điều khiển 29
else
{
False_Expression ; }
Boolean_Expression biểu thức logic tập hợp biểu thức logic, Boolean_Expression có giá trị true câu lệnh True_Expression thực hiện, biểu thức câu lệnh đơn tập hợp câu lệnh khác Nếu True_Expression câu lệnh đơn, bạn bỏ cặp dấu ngoặc mà C++ khơng báo lỗi Nếu Boolean_Expression trả giá trì false câu lệnh False_Expression thực
Chương trình hồn chỉnh ví dụ // Chuong trinh minh hoa cau lenh if -else
2 # include <iostream > using namespace std; int main( )
5 {
6 int hours ;
7 double gross_pay , rate;
8 cout << " Enter the hourly rate of pay: $"; cin >> rate;
10 cout << " Enter the number of hours worked ,\n" 11 << " rounded to a whole number of hours : "; 12 cin >> hours ;
13 if ( hours > 40)
14 gross_pay = rate * 40 + 1.5 * rate * ( hours - 40); 15 else
16 gross_pay = rate * hours ; 17 cout.setf(ios :: fixed );
18 cout.setf(ios :: showpoint ); 19 cout precision (2);
20 cout << " Hours = “ << hours << endl;
21 cout << "Hourly pay rate = $" << rate << endl; 22 cout << "Gross pay = $" << gross_pay << endl;
23 return 0;
24 }
Hình 2.5: Chương trình minh họa cấu trúc if-else Vòng lặp
Hầu hết chương trình chứa câu lệnh thực lặp lại nhiều lần Ví dụ, giả thiết bạn ông chủ cơng ty lớn, bạn cần phải tính lương trả cho nhân viên tuần, giả sử bạn có 1000 nhân viên, bạn phải thực phép tính 1000 lần Thơng thường, chương trình mình, bạn phải viết chúng lặp lặp lại 1000 lần Tuy nhiên, C++ cung cấp cho cấu trúc cho phép thực việc vài câu lệnh đơn giản, chúng gọi vòng lặp Trong phần này, xem xét đến vòng lặp while
Cấu trúc lặp while cho phép bạn thực câu lệnh phần “body” biểu thức Boolean_Expression trả giá trị true Cú pháp sau:
(38)dành
cho
hội
đồng
nghiệm
thu
30 Một số khái niệm C++
}
Chương trình sau đưa hình chữ “Hello” với số lần bạn nhập từ bàn phím sử dụng cấu trúc lặp while
1 // Chuong trinh minh hoa lap while # include <iostream >
3 using namespace std; int main( )
5 {
6 int count_down ;
7 cout << "How many greetings you want? "; cin >> count_down ;
9
10 while ( count_down > 0) 11 {
12 cout << " Hello ";
13 count_down = count_down - 1; 14 }
15 cout << endl;
16 cout << "That 's all !\n"; 17 return 0;
18 }
Hình 2.6: Chương trình minh họa vịng lặp while
Cấu trúc while hoạt động dựa vào giá trị Boolean_Expression, thực thi, hệ điều hành kiểm trả điều kiện Boolean_Expression trước, Boolean_Expression có giá trị true hệ điều hành thực câu lệnh “body” Việc có ưu điểm Boolean_Expression kiểm tra trước thực biện câu lệnh “body” Tuy nhiên , số trường hợp bạn muốn hiển thị menu chẳng hạn, bạn cần thực câu lệnh “body” lần dù Boolean_Expression True hay False Vậy bạn phải làm nào? Rất may, C++ cung cấp lựa chọn thay cho while, cấu trúc do-while Với cấu trúc này, câu lệnh “body” thực lần Cú pháp do-while sau:
do {
Body statement ;
} while( Boolean_Expression );
Khi thực thi, hệ điều hành thực thi câu lệnh “body” trước kiểm tra Boolean_Expression Ví dụ
1 // Chuong trinh minh hoa lap - while # include <iostream >
3 using namespace std; int main( )
5 {
6 char ans; do
8 {
9 cout << " Hello \n";
10 cout << "Do you want another greeting ?\n" 11 << "Press y for yes , n for no ,\n"
(39)dành
cho
hội
đồng
nghiệm
thu
2.5 Phong cách lập trình 31
13 cin >> ans;
14 } while (ans == 'y' || ans == 'Y'); 15 cout << "Good -Bye\n";
16 return 0; 17 }
Hình 2.7: Chương trình minh họa vịng lặp do-while
2.5 Phong cách lập trình
Tất biến giáo trình lựa chọn để làm tiêu chuẩn cho chương trình khác Các chương trình, đoạn mã đặt định dạng cụ thể thống Ví dụ khai báo, câu lệnh đặt thụt vào khoảng Một chương trình viết cẩn thận khơng logic mà cịn thống hình thức thể đoạn mã lệnh dễ dàng cho người khác đọc, hiểu sửa lỗi có Việc thay đổi chương trình trở nên dễ dàng
Một chương trình dễ dàng đọc hơn, tất nhiên người, đoạn mã lệnh đặt cách khoa học, đoạn mã nên đặt thụt vào khoảng so với lề bố trí chúng theo nhóm
Cặp dấu sử dụng để phân biệt đoạn mã dài chương trình lớn Bạn nên đặt chúng dòng, dấu mở ngoặc ) đóng ngoặc ) nên thụt dịng cách lề khoảng định đó, việc giúp bạn dễ dàng tìm cặp ngoặc tương ứng
Để người khác dễ dàng hiểu chương trình, sử dụng giải để giải thích ngắn gọn đoạn mã viết C++ hầu hết ngôn ngữ lập trình khác cung cấp cú pháp để viết giải chương trình Trong C++, sử dụng kí tự // để bắt đầu thích với nội dung dịng Nếu thích nhiều dịng bạn sử dụng nhiều kí tự // sử dụng cặp kí tự /* để bắt đầu */ để kết thúc Ví dụ sau:
/* Đây chương trình tính Uscln số. Sử dụng thuật tốn Euclid */
Cần ý rằng, tất giải khơng trình biên dịch xử lý
Trong lập trình, đặc biệt chương trình lớn, bạn phải làm việc với số lượng lớn biến Trong số chúng, số có giá trị khơng thay đổi tồn q trình chương trình thực thi, chúng gọi số C++ cung cấp từ khóa const cho phép bạn khai báo biến với vai trò số Ví dụ,
const int WINDOW_COUNT =10;
Các số thường viết hoa toàn kí tự, chúng gồm nhiều từ nối với dấu gạch (_) Thông thường, chúng đặt đầu chương trình để thuận tiện cho việc kiểm soát thay đổi
(40)dành
cho
hội
đồng
nghiệm
thu
32 Một số khái niệm C++
2.6 Biên dịch chương trình với GNU/C++
Trong mơi trường tích hợp nhà sản xuất chương trình dịch cung cấp, bạn vừa soạn thảo chương trình, vừa dịch, liên kết chạy chương trình kết hợp lúc (chỉ cần bạn nhấn phím tắt - ví dụ F9 số phiên Dev-Cpp) Tuy nhiên, có lúc bạn khơng có sẵn mơi trường tích hợp cần dịch chương trình, dự án lớn file thực thi (*.exe), bạn cần có chương trình dịch Ở đây, chúng tơi trình bày cách dịch chương trình với dịch GNU/C++
GNU/C++
Bộ trình dịch GNU (GCC: GNU Compiler Collection) tập hợp trình dịch thiết kế cho nhiều ngơn ngữ lập trình khác nhau, nhiều hệ điều hành chấp nhận Tên gốc GCC GNU C Compiler (Trình dịch C GNU), ban đầu hỗ trợ dịch ngơn ngữ lập trình C GCC 1.0 phát hành vào năm 1987, sau mở rộng hỗ trợ dịch C++ vào tháng 12 năm tiếp tục mở rộng hỗ trợ dịch ngôn ngữ khác Fortran, Pascal, Objective C, Java, Ada Bạn tải miễn phí mạng Trong số mơi trường tích hợp Dev-Cpp trình dịch GNU/C++ cài đặt sẵn (thư mục bin)
Câu lệnh dịch
Từ cửa sổ lệnh hệ điều hành thư mục chứa dịch GNU (đã cài đặt) bạn gõ câu lệnh sau:
g++ options source_file
trong đó, source_file file bạn cần biên dịch (viết đầy đủ đường dẫn thư mục, tên lẫn phần mở rộng ) options lựa chọn chế độ dịch, đặt trước sau source_file Tên file kết ngầm định a.exe (trong môi trường DOS/Windows) a.out (trong môi trường Unix/Linux) Để đặt tên cụ thể cho file kết bạn sử dụng options:
-o target_file
Ví dụ: g++ main.cpp -o main.exe dịch file main.cpp mã máy, chạy đặt vào file có tên main.exe
Dịch chương trình nhiều file
Để dịch chương trình nhiều file, bạn liệt kê danh sách file cần dịch liên kết vào câu lệnh, GCC tạo file thực thi cuối theo ý muốn Ví dụ, ta có chương trình với hàm tính cộng (sum), trừ (sub), nhân (mul), chia (div) hai số nguyên hàm main dùng để tính biểu thức (a + b) * (a - b) với a = b = Giả sử hàm đặt file có tên tên hàm đuôi cpp đoạn mã bên
1 /* file "sum.cpp" */
2 int Sum(int value1 , int value2 ) {
4 return value1 + value2 ; }
6
7 /* file "sub.cpp" */
8 int Sub(int value1 , int value2 ) {
(41)dành
cho
hội
đồng
nghiệm
thu
2.6 Biên dịch chương trình với GNU/C++ 33
12
13 /* file "mul.cpp" */
14 int Mul(int value1 , int value2 ) 15 {
16 return value1 * value2 ; 17 }
18
19 /* file "div.cpp" */
20 int Div(int value1 , int value2 ) 21 {
22 return value1 / value2 ; 23 }
Hình 2.8: Chương trình với nhiều file /* file "main.cpp" */
2 # include <iostream > using namespace std;
5 int Sum(int, int); int Sub(int, int); int Div(int, int); int Mul(int, int);
10 int main () {
11 int a = 5, b = 3;
12 cout << Mul(Sum(a, b), Sub(a, b)); 13 return 0;
14 }
Hình 2.9: Hàm main
Khi đó, bạn dùng câu lệnh: g++ main.cpp sum.cpp sub.cpp div.cpp mul.cpp -o main.exe để dịch chương trình file main.exe
Với câu lệnh dịch thực bước: dịch file sang mã object (cùng tên file với đuôi *.o) sau liên kết file thành file mã thực thi (đi *.exe)
Bạn tách rời hai bước cách dịch sang mã object file (với lựa chọn options -c) sau thực liên kết chúng Ví dụ:
Bước 1: Dịch sang object
g++ -c main.cpp // cho file main.o
g++ -c sum.cpp // cho file sum.o
g++ -c sub.cpp // cho file sub.o
g++ -c mul.cpp // cho file mul.o
g++ -c div.cpp // cho file div.o
Bước 2: Liên kết object
g++ main.o sum.o sub.o div.o mul.o -o main.exe
Tiện ích Make
(42)dành
cho
hội
đồng
nghiệm
thu
34 Một số khái niệm C++
Makefile gồm nhóm thơng tin, nhóm đặc tả file kết cuối cùng, nhóm cịn lại đặc tả file thành viên Mỗi đặc tả gồm dòng Dòng đầu gồm: tên file đích (*.o), dấu hai chấm file liên quan cần để dịch file Dòng thứ hai câu lệnh dịch bắt đầu dấu TAB Riêng nhóm all : danh sách file *.o cần liên kết để file cuối Ví dụ, makefile để dịch ví dụ sau:
1 all : main.o sum.o sub.o mul.o div.o
2 g++ main.o sum.o sub.o mul.o div.o -o main.exe main.o : main.cpp
4 g++ -c main.cpp sum.o : sum.cpp g++ -c sum.cpp sub.o : sub.cpp g++ -c sub.cpp mul.o : mul.cpp 10 g++ -c mul.cpp 11 div.o : div.cpp 12 g++ -c div.cpp 13 clean :
14 rm *.o ; rm main.exe
Hình 2.10: Makefile
(43)dành
cho
hội
đồng
nghiệm
thu
2.6 Biên dịch chương trình với GNU/C++ 35
Bài tập
1 Trong toán phần đây, cho biết sử dụng vòng lặp hợp lý (for, while, do-while):
(a) Tính tổng dãy số sau: 1/2 + 1/3 + + 1/2016
(b) Đọc danh sách điểm thi môn học sinh viên
(c) Kiểm tra điều kiện đầu vào biến nguyên dương a, b,c thỏa mãn điều kiện không vượt 1000
(d) Kiểm tra hàm xem thực giá trị khác truyền vào tham số hàm
2 Tìm ước số chung lớn số nguyên nhập từ bàn phím Cho biết kết in hình đoạn chương trình đây:
int n = 1024;
int log = 0;
for (int i = 1; i < n; i = i * 2); log ++;
cout << n << " " << log << endl;
4 Viết chương tính ex xấp xỉ theo công thức sau: ex = + x + x2/2! + x3/3! + + xn/n!
5 Viết chương trình in số nguyên tố nhỏ n (n số nguyên dương, nhập từ bàn phím)
6 Giá trị Pi tính xấp xỉ theo công thức sau:
Pi = 4[ – 1/3 + 1/5 – 1/7 + 1/9 + + ((–1)n)/(2n + 1)]
Hãy viết chương trình tính xấp xỉ giá trị Pi theo cơng thức Chương trình nhập vào giá trị nguyên n in giá trị Pi hình Chương trình cho phép người dùng lặp lại việc tính tốn Pi với giá trị n người dùng dừng chương trình kết thúc
7 Viết chương trình đổi năm (giữa 1000 3000) từ số la mã sang số thập phân thơng thường Chương trình cho phép người dùng lặp lại việc tính tốn chuyển đổi năm muốn dừng chương trình kết thúc
Ví dụ: MCLM → 1950, MCMLXXXIX → 1989
8 Quang chọn số làm mật cho thể ngân hàng Hãy giúp Quang nhớ lại mật biết mật số có chữ số thỏa mãn điều kiện sau:
(a) Tất chữ số khác
(b) Chữ số hành nghìn lần chữ số hàng chục (c) Là số lẻ
(44)dành
cho
hội
đồng
nghiệm
(45)dành
cho
hội
đồng
nghiệm
thu
Chương 3
Kiểm thử gỡ rối chương trình
Chương giới thiệu kỹ thuật kiểm thử gỡ rối viết chương trình lập trình để có lỗi
3.1 Kỹ thuật kiểm thử
Kiểm thử thành phần phát triển phần mềm để đảm bảo độ tin cậy chất lượng phần mềm Phần mềm kiểm thử theo nhiều cách khác nhau, số cách thực người lập trình (developer) số khác thường thực người chuyên kiểm thử (tester) Cụ thể sau:
• Kiểm thử đơn vị (Unit testing): thi hành lớp, thị hoàn chỉnh chương trình nhỏ mà viết lập trình viên nhóm mà q trình kiểm thử tách biệt;
• Kiểm thử thành phần (Component testing): thi hành lớp, modul chương trình nhỏ phát triển nhiều người lập trình nhóm lập trình mà q trình kiểm thử tách biệt so với hệ thống phần mềm hồn chỉnh;
• Kiểm thử tích hợp (Integration testing): kết hợp thành phần phần mềm kiểm tra phần mềm hoàn thành Trong kiểm thử đơn vị kiểm tra thành phần đơn vị riêng lẻ kiểm thử tích hợp kết hợp chúng lại với kiểm tra giao tiếp chúng;
• Kiểm thử hồi qui (Regression testing): Đơn kiểm tra lại phần mềm sau có sự thay đổi xảy ra, để bảo đảm phiên phần mềm thực tốt chức phiên cũ thay đổi không gây lỗi chức vốn làm việc tốt;
• Kiểm thử hệ thống (System testing):
Trong phần này, kiểm thử đề cập kiểm thử lập trình viên, bao gồm kiểm thử đơn vị, kiểm thử thành phần kiểm thử tích hợp
(46)dành
cho
hội
đồng
nghiệm
thu
38 Kiểm thử gỡ rối chương trình
Kiểm thử hộp trắng kỹ thuật mà người kiểm thử biết rõ công việc thực bên hạng mục kiểm thử Đây kỹ thuật kiểm thử mà bạn người phát triển phần mềm sử dụng để kiểm tra mã nguồn bạn viết
Một số lập trình viên sử dụng thuật ngữ “Kiểm thử” “Gỡ rối” thay đổi cho khái niệm khác Kiểm thử có nghĩa phát lỗi cịn gỡ rối có nghĩa chuẩn đốn, định vị sửa nguyên nhân lỗi mà phát
Ở đây, chúng tơi trình bày số kỹ thuật kiểm thử kiểm thử đơn vị
3.1.1 Kiểm thử viết mã nguồn
Vấn đề lỗi sớm tìm thấy tốt việc viết mã nguồn Nếu bạn nghĩ cách hệ thống viết nó, bạn kiểm chứng thuộc tính đơn giản chương trình xây dựng Với kết mà mã nguồn bạn thơng qua vịng kiểm thử trước biên dịch chắn loại lỗi giảm thiểu cách đáng kể
Kiểm thử mã nguồn cận (boundaries) nó.
Một kỹ thuật kiểm thử điều kiện cận: phần nhỏ mã nguồn viết cấu trúc vòng lặp cấu trúc điều khiển Quá trình gọi kiểm thử điều kiện cận thường kiểm tra cận cách tự nhiên chương trình liệu, ví dụ đầu vào khơng tồn rỗng, cỡ mảng, …Ý tưởng hầu hết lỗi thường xuất cận Nếu phần mã nguồn hỏng, có khả lỗi cận Ngược lại thi hành tốt cận cũng khả thi hành tốt chỗ khác Đoạn chương trình với hàm fgets() đọc ký tự gặp ký tự dòng (newline) đầy đệm:
int i;
char s[MAX ];
for (i = 0; (s[i] = getchar ()) !=' \n' && i < MAX -1; ++i) s[ i]= ‘’\0;
Hãy tưởng tượng bạn vừa viết vòng lặp Cận kiểm thử đơn giản: dòng trống Nếu bạn bắt đầu với dòng mà gồm ký tự xuống dịng newline, dừng vịng lặp với i = câu lệnh gán i = -1 s[-1] gán ký tự NULL Điều gây lỗi cận mảng Nếu viết lại vòng lặp sau:
for (i = 0; i < MAX -1; i++)
if ((s[i] = getchar ()) == ‘\’n)
break; s[i] = '\0 ';
Lặp lại với kiểm thử ban đầu (dòng rỗng), dễ dàng xác nhận ký tự xuống dòng (newline) xử lý Nhưng xảy đầu vào rỗng lời gọi với hàm getchar() sẽ trả EOF Vì vậy, phải kiểm tra điều đó:
for (i = 0; i < MAX -1; i++)
if ((s[i] = getchar ()) == ‘\’n || s[i]== EOF)
break; s[i] = '\0 ';
(47)dành
cho
hội
đồng
nghiệm
thu
3.2 Kỹ thuật gỡ rối chương trình 39
Kiểm tra điều kiện trước sau.
Một cách khác để kiểm thử chương trình phải kiểm chứng đặc tính cần thiết mong đợi nắm giữ trước sau số phần thi hành mã nguồn Đảm bảo chắn giá trị đầu vào phạm vi ví dụ thường thấy kiểm thử điều kiện trước Dưới đây, hàm avg( ) tính giá trị trung bình n phần tử mảng có vấn đề n nhỏ bằng 0 :
double Avg(double a[], int n) {
double sum; sum = 0.0;
for (int i = 0; i < n; i++) sum += a[i];
return sum/n; }
Hàm avg làm n Mảng với không phần tử khái niệm hợp lệ giá trị trung bình khơng xác định Để kiểm thử điều này, cho giá trị trung bình 0.0 n nhỏ 0:
return (n <= 0) ? 0.0 : sum/n;
3.2 Kỹ thuật gỡ rối chương trình
3.2.1 Khái niệm vế gỡ rối chương trình
Theo định nghĩa chung, gỡ rối q trình có phương pháp để tìm giảm thiểu số lỗi khiếm khuyết chương trình máy tính
Tất nguồn gốc lỗi bắt nguồn từ tiền đề bản: nghĩ thực sai Bởi theo nguyên tắc đơn giản này, thực số lỗi kỳ lạ phi logic đó, việc gỡ rối phần mềm cơng việc khó khăn thách thức
3.2.2 Phân loại lỗi
Lỗi chia thành loại lỗi chính:
• Lỗi cú pháp: Dễ dàng phát trình biên dịch biên dịch chương trình Nếu câu lệnh mắc lỗi cú pháp, khơng biên dịch chương trình bạn khơng thực thi;
• Lỗi thực thi: Lỗi thực thi xảy máy tính lệnh thực hành động lỗi Khi xảy lỗi run-time, máy tính dừng thực thi chương trình hiển thị thơng báo chuẩn đốn dịng lệnh gây lỗi;
(48)dành
cho
hội
đồng
nghiệm
thu
40 Kiểm thử gỡ rối chương trình
3.2.3 Một số kỹ thuật gỡ rối
1 Khai thác đặc trưng trình biên dịch.
Trình biên dịch tốt làm số phân tích tĩnh mã nguồn Phân tích mã tĩnh phân tích phần mềm mà thực khơng cần chương trình thực thi dịch từ mã nguồn phần mềm Phân tích giúp phát vấn đề ngữ nghĩa sai kiểu, mã chết (code dead)
Ví dụ gcc, trình biên dịch chuẩn ngơn ngữ C hệ thống GNU/Linux có số lựa chọn mà ảnh hưởng tới phân tích tĩnh thực Chúng thường chia làm loại: lựa chọn cảnh báo cờ tối ưu Theo lựa chọn cảnh báo đề cập, danh sách số lựa chọn: Wall, Wshadow, …
Trình biên dịch hỗ trợ số vấn đề tối ưu Một số kích hoạt trình biên dịch để làm phân tích dịng mã kỹ loại bỏ mã chết Tuy nhiên lập trình viên phải hiểu cơng việc tối ưu mức độ chống lại việc gỡ rối Tối ưu trình phân tích dịng mã xếp lại mã lệnh Điều có nghĩa tối ưu, mã khác từ mã viết ban đầu, điều khó khăn, chí khơng thể gỡ rối Vì vậy, cờ tối ưu nên bật mã gỡ rối xong
2 Kỹ thuật gỡ rối dựa vào sử dụng cout.
Kỹ thuật cout xuất tên nội dung cần gỡ rối từ lệnh C++ vào thiết bị chuẩn hình file Nó bao gồm thêm lệnh xuất (print) mã để theo dõi luồng điều khiển Đây kỹ thuật gỡ rối thông dụng, đặc biệt với người lập trình Ví dụ minh họa kỹ thuật Lệnh xuất (printing) không nên sử dụng trực tiếp: nên định nghĩa macro để chuyển đổi chế độ gỡ rối thuận tiện
1 # ifndef DEBUG_H
3 # define DEBUG_H
5 # include <stdarg h>
7 #if defined ( NDEBUG ) && defined ( GNUC )
9 # define Pmesg(level , format , args ) ( ( void ) ) 10
11 #else 12
13 void Pmesg (int level , char * format , ) ; 14
15 /* p r i n t a message , i f i t i s c o n s i d e r e d s i g n i f i c a n t
enough Adapted
16 from [ ] , p 174 */ 17
18 # endif 19
20 # endif / * DEBUG_H * /
(49)dành
cho
hội
đồng
nghiệm
thu
3.2 Kỹ thuật gỡ rối chương trình 41
1 # include <stdio h> # include " debug1 h"
4 extern int msglevel ;
6 #if defined ( NDEBUG ) && defined ( GNUC )
8 #else
9 void Pmesg (int level , char *format , ){ 10
11 # ifdef NDEBUG 12 #else
13 va_list args;
14 if ( level >msglevel )
15 return;
16 va_start (args , format );
17 vfprintf (stderr ,format ,args); 18 va_end (args);
19
20 # endif 21 # endif 22 }
Hình 3.2: Ví dụ kỹ thuật gỡ rối sử dụng cout - file thi hành.
Ở đây, mgslevel biến toàn cục định nghĩa để điều khiển gỡ rối in bao nhiêu. Sau đó, pmesg(100, "Foo is %l\n", foo) sử dụng để in giá trị foo trong trường hợp msglevel nhận giá trị 100 lớn Chú ý rằng, tất mã gỡ rối trong thi hành bỏ qua cách thêm –DNDEBUG vào cờ tiền xử lý
3 Logging.
Logging khái niệm in thơng điệp, trình bày phần trước, nhiên khái quát Logging trợ giúp thơng dụng cho gỡ rối Một thử lần để giải số vấn đề liên quan đến hệ thống biết file log hữu ích Logging nghĩa tự động ghi lại thông điệp thông tin kiện để giám sát trạng thái chương trình bạn chuẩn đoán vấn đề gặp phải Logging giải pháp thực cho kỹ thuật cout
4 Assertions.
Assertions biểu thức để đánh giá điểm náo mã nguồn Nếu assertion sai lỗi tìm thấy Viết assertions mã nguồn tạo cho giả định tường minh Trong C/C++ file assert.h phải thêm vào chương trình biểu thức bạn muốn khẳng định phải viết đối số macro, ví dụ assert(var > 0) Chương trình hủy bỏ khẳng định thất bại thông báo lỗi đưa xác dịng mã file sử dụng Assertion Bởi assert macro nên dễ dàng bỏ qua phiên cuối biên dịch mã chương trình (dịch chế đô không gỡ rối) Nếu sử dụng g++, bạn phải sử dụng cờ tiền xử lý –DNDEBUG
(50)dành
cho
hội
đồng
nghiệm
thu
42 Kiểm thử gỡ rối chương trình
Các cơng cụ gỡ rối cho phép làm việc thơng qua dịng lệnh mã để tìm lỗi đâu Nó cho phép làm việc tương tác, kiểm soát việc thực chương trình, dừng chương trình thời điểm khác nhau, kiểm tra biến, thay đổi dòng mã thi hành chương trình Để sử dụng cơng cụ gỡ rối, chương trình phải biên dịch với thông tin gỡ rối chèn vào Thông tin cung cấp kí hiệu gỡ rối thơng qua chương trình dịch mã nhị phân Ký hiệu gỡ rối mô tả hàm biến đâu nhớ Thi hành chương trình với ký hiệu gỡ rối chạy bình thường, nhiên chạy chậm đôi chút
3.2.4 Giải pháp vấn đề liên quan đến C/C++
Phần này, tập trung vào vấn đề xuất lập trình với ngơn ngữ C C++ Hiện nay, C++ ngôn ngữ phổ biến giải tốn phức tạp thích hợp để minh họa thực số kỹ thuật gỡ rối cho tốn chung mà lập trình viên ít kinh nghiệm thường gặp phải lập trình Quá trình xây dựng chương trình C++. Trước kiểm tra vấn đề phổ biến tạo lập trình C/C+, hữu ích để tóm tắt lại bước liên quan việc xây dựng chạy chương trình C++/C Chương trình C++/C xây dựng bước, ví dụ chia thành bước nhỏ Trong mơi trường Unix, xây dựng chương trình chia thành bước:
• Tiền xử lý: Trong pha bao gồm file header macro xử lý; đầu pha tiền xử lý mã nguồn C/C++
• Biên dịch: Biên dịch dịch mã nguồn C/C++ thành mã hợp ngữ.
• Hợp ngữ: Mã hợp ngữ dịch thành mã đối tượng nhị phân Kết thường file với phần mở rộng o
• Liên kết: Nhiệm vụ liên kết kết hợp file đối tượng thư viện thành file thi hành. • Nạp liên kết động: Bước cuối bao gồm nạp thư viện (hoặc phần thư viện) được
yêu cầu liên kết động để chạy file thi hành Cấp phát nhớ động.
Trong C/C++, lập trình viên cấp phát giải phóng nhớ động cách minh bạch (thông qua malloc/free hoặc new/delete ) Nếu nhớ cấp phát giải phóng sai gây lỗi lúc chạy chương trình (mất nhớ, hỏng nhớ, …)
(51)dành
cho
hội
đồng
nghiệm
thu
3.2 Kỹ thuật gỡ rối chương trình 43
• Thư viện thêm vào liên kết với chương trình thực thi; • Chương trình thực thi mà điều khiển việc thực thi chương trình Kiểm tra lời gọi hệ thống.
System call tracer chương trình cho phép kiểm tra vấn đề ranh giới mã bạn và hệ điều hành Chương trình người sử dụng tương tác trực tiếp với nhân hệ điều hành Nếu chương trình muốn truy cập đĩa cứng, khơng thể thực trực tiếp mà phải gọi hàm hệ thống thích hợp chịu trách nhiệm chuyển liệu chương trình đĩa cứng Để tìm hàm sử dụng chương trình, thường sử dụng cơng cụ System call tracer
Công cụ System call tracer chuẩn GNU/Linux gọi strace Đây công cụ mạnh mà đưa tất lời gọi hệ thống sử dụng chương trình người dùng Dưới ví dụ sử dụng strace để phát lỗi chương trình
1 # include <iostream > # include <string> # include <fstream > # include <cstdlib >
6 using namespace std;
7 int main(int argc , char * argv[ ]) {
9 string filename ; 10 string basename ; 11 string extname ; 12 string tmpname ;
13 const string suffix ("tmp"); 14 /* for each commandline
15 argument ( which i s an o r d i n a r y Cs t r i n g ) */ 16
17 for (int i = 1; i < argc; ++i) 18 {
19 filename = argv[i]; // p r o c e s s argument as f i l e name
20 string:: size_type idx = filename find('.'); // search period in name 21 if (idx == string:: npos)
22 {
23 //f i l e name does n o t c o n t a i n any p e r i o d 24 tmpname = filename ; // HERE IS THE ERROR
25 // tmpname = filename + '.' + suffix ;
26 }
27 else tmpname = filename ;
28 // p r i n t f i l e name and t emporar y name
29 // c o u t << f i l e n ame << " => " << tmpname << e n d l ; / / USEFUL 30 }
31 ifstream file( tmpname c_str ()) ; 32 if(! file)
33 {
34 cerr << "Can 't open input file \"" << filename << ".tmp \"\n"; 35 exit( EXIT_FAILURE );
36 }
37 char c ;
(52)dành
cho
hội
đồng
nghiệm
thu
44 Kiểm thử gỡ rối chương trình
39 cout.put(c); 40 }
Hình 3.3: Ví dụ kỹ thuật gỡ rối sử dụng strace
Đây chương trình hồn chỉnh, biên dịch với g++ chạy sau:
g++ -o straceTest vidu3_chuong3 cpp
Chương trình đơn giản cố gắng truy cập file văn với phần mở rộng tên file tmp nằm thư mục Tên file mở phải đưa vào tham số dòng lệnh Chương trình gắn thêm phần hậu tố vào tên mở file Để thực chương trình này, file văn với tên thích hợp (ví dụ list.tmp) phải tạo trước thư mục với file chương trình
Chạy chương trình sinh thơng báo lỗi Can't open input file "list.tmp" Chương trình khơng thể tìm thấy file đầu vào, tương lạ file đầu vào tồn thư mục Như chương trình có lỗi
Chúng ta sử dụng công cụ strace để tìm lỗi chương trình Bắt đầu strace với tham số dòng lệnh:
strace –o strace out / straceTest list
Đầu strace viết file strace.out Ví dụ sau:
open (" list", O_RDONLY | O_LARGEFILE ) = -1 ENOENT (No such file or directory ) write (2, "Can\'t open input file", 23) = 23
write (2, "list", 4) = 4 write (2, " tmp \"\n", 6) = 6 exit_group (1) = ?
Kiểm tra danh sách lời gọi hệ thống, thấy vấn đề lỗi tên file tham số hàm mở file cho biết cố gắng mở file list list.tmp
3.3 Lập trình khơng lỗi
Lập trình khơng lỗi gần việc khơng thể làm Theo qui luật ”Mọi chương trình có lỗi” Vì vậy, ta cần lập trình lỗi Dưới số qui tắc để lập trình lỗi nhất:
• Thiết kế chương trình dễ hiểu, đơn giản Nếu làm việc hệ thống có sẵn cần hiểu kỹ cấu trúc hệ thống này;
• Kiểm tra kỹ dịng mã viết;
(53)dành
cho
hội
đồng
nghiệm
thu
3.3 Lập trình khơng lỗi 45
Bài tập
1 Kiểm thử chương trình sau với điều kiện cận (a) Hàm tính giai thừa:
int factorial (int n) {
int fac; fac = 1;
while (n ) fac *=n;
return fac; }
(b) Hàm copy xâu ký tự từ trỏ nguồn src vào trỏ đích dest: void strcpy (char *dest , char *scr)
{
for (int i = 0; src[i] != '\0';i++) dest[i] = src[i];
}
(c) Một hàm copy xâu ký tự khác copy n ký tự từ s vào t : void strncpy (char *t, char *s, int n)
{
while (n > && *s != '\0 '){ *t = *s;
t++; s++; n ; }
(54)dành
cho
hội
đồng
nghiệm
(55)dành
cho
hội
đồng
nghiệm
thu
Chương 4 Hàm
4.1 Thiết kế từ xuống (top-down)
Làm để bẻ gãy bó đũa Hay làm để “ăn” hết voi (câu đố toán cổ) Câu trả lời thật dễ dàng: chia nhỏ bó đũa bẻ gãy một, tương tự chia nhỏ voi ăn phần Đầu tiên ta chia voi thành phần: đầu, mình, tứ chi đuôi Dĩ nhiên lúc giải hết phần đầu, ta lại chia nhỏ đầu voi thành phận: tai mắt, mũi, họng, vòi … , chí vịi q dài ta lại “phân khúc” tiếp Đây ý tưởng việc thiết kết thuật toán từ xuống, tức chia nhỏ toán cần giải thành nhiều phần, lại tiếp tục chia nhỏ phần để có phần nhỏ hơn, tiếp tục toán trở thành tập hợp toán nhỏ, đủ nhỏ để ta giải trực tiếp khơng cần phải chia Ơng tổ C/C++ đưa ví dụ viết chương trình để máy đánh cờ với người Thoạt đầu, ta cảm thấy tốn khó, khó biết phải đâu, nhiên kiên trì “chia nhỏ” tốn đến lúc ta thấy giải tốn có lẽ khơng có khó khăn Đó ưu điểm chiến lược thiết kế top-down (thiết kế từ xuống)
Trong thực tế, thiết kế lập trình từ xuống thường kết hợp với thiết kế lập trình từ lên Trong tiếp cận từ lên, giải toán đơn giản, “trường hợp riêng” toán tổng quát lưu lại kết quả, sau kết hợp dần kết quả, nghiệm toán nhỏ thành nghiệm toán lớn tiếp tục q trình để thu nghiệm tốn cuối
(56)dành
cho
hội
đồng
nghiệm
thu
48 Hàm
4.2 Hàm
4.2.1 Ý nghĩa hàm
Hàm chương trình chương trình lớn Nhiệm vụ hàm giống chương trình giải hồn chỉnh tốn (con) Do vậy, hàm làm việc theo cách chung: nhận không, nhận nhiều đầu vào (là tham đối), xử lý đầu vào này cuối trả lại (hoặc không) giá trị kết cho nơi gọi đến Như vậy, chương trình C/C++ tập hợp hàm, ln ln có hàm với tên gọi main(), chạy chương trình, hàm main() chạy gọi đến hàm khác Kết thúc hàm main() kết thúc chương trình.
Hàm giúp cho việc phân đoạn chương trình thành mơđun riêng rẽ, hoạt động độc lập với ngữ nghĩa chương trình lớn, có nghĩa hàm sử dụng chương trình mà sử dụng chương trình khác Khi hàm viết hoàn chỉnh để thực nhiệm vụ người sử dụng khơng cần quan tâm đến hàm làm việc nào mà cần biết hàm cần (input) giải việc (output) để sử dụng vào chương trình của Ví dụ cần tính bậc x, người sử dụng cần viết sqrt(x) tin tưởng kết trả lại chắn bậc x mà không cần quan tâm hàm thực nào kết
Về mặt kỹ thuật, hàm có số đặc trưng:
• Nằm ngồi văn (file) có chương trình gọi đến hàm Trong văn chứa nhiều hàm Đối với người học ban đầu, viết tất hàm (cùng với hàm main() ) file văn
• Được gọi (sử dụng) từ chương trình ( main() ), từ hàm khác từ Trường hợp hàm lại gọi đến ta gọi hàm đệ quy
• Có cách truyền liệu (đầu vào) cho hàm: Truyền theo giá trị (đối biến có kiểu thơng thường), truyền theo tham chiếu (đối biến tham chiếu) truyền theo dẫn trỏ (đối trỏ)
4.2.2 Cấu trúc chung hàm
Cấu trúc hàm bố trí giống hàm main() Cụ thể cú pháp một hàm viết sau:
type_returned function_name (list of arguments ) {
declarations ; // kiểu, hằng, biến, phục vụ riêng cho hàm này statements ; // các câu lệnh
return ( expression ); // cũng nằm vị trí khác }
(57)dành
cho
hội
đồng
nghiệm
thu
4.2 Hàm 49
• Danh sách tham đối hình thức cịn gọi ngắn gọn danh sách đối (list of arguments) gồm dãy đối cách dấu phẩy, đối biến thường, biến tham chiếu biến trỏ, hai loại biến sau ta trình bày phần tới Mỗi đối khai báo giống khai báo biến, tức cặp gồm <type> <argument_name>.
• Tên biến khai báo thân hàm khơng trùng với tên đối
• Câu lệnh return nằm vị trí Khi gặp return chương trình tức khắc khỏi hàm trả lại giá trị biểu thức sau return (nếu có) Nếu khơng có câu lệnh return
hoặc sau câu lệnh return khơng có biểu thức kiểu hàm phải khai báo void.
Đoạn mã4.1 hàm in 10 dấu '*' Do hàm làm cơng việc đơn giản in hình và không trả lại giá trị nên ta xây dựng hàm không đối kiểu giá trị trả lại void.
1 void PrintTenAsterisks () {
3 for (int count = 1; count <= 10; count ++) cout << "*";
5 cout << endl;
6 return ; // khong tra lai ket qua tinh toan nao , chi ket thuc }
Hình 4.1: Xây dựng hàm để in 10 dấu '*'.
Vai trò đối hàm (như x hàm sqrt(x), ) “cữa ngõ” để hàm giao tiếp với NSD theo nghĩa: hàm xử lý với số liệu vào (là đối) theo ý muốn NSD Từ đó, ta mở rộng hàm phép in dãy dấu '*' với độ dài cách thêm cho hàm đối đại diện cho số dấu cần in Ta hàm Hình 4.2
1 void PrintAsterik (int numAsteriks ) {
3 int count ;
4 for (count = 1; count <= numAsteriks ; count ++) cout << "*" ;
6 cout << endl; return ; }
Hình 4.2: Hàm in chuỗi dấu '*' với độ dài tham số
Trong hàm PrintAsterik(int numAsteriks) , ta đưa thêm tham đối numAsteriks với nghĩa hàm in numAsteriks dấu '*', từ vịng lặp for thay cho biến đếm count chạy cố định từ đến 10 hàm PrintTenAsteriks(), count chạy từ 1 đến numAsteriks Như vậy, hàm PrintAsterik() cung cấp cách in dãy '*' mềm dẻo hàm PrintTenAsterik() Để in dãy 10 dấu '*' có thể gọi hàm PrintTenAsterik() hoặc PrintAsterik(10), nhiên để in dãy 12 dấu '*' hàm PrintTenAsterik() khơng dùng
Dưới chương trình minh họa cách sử dụng hàm PrintAsterik() để in tam giác cân gồm toàn dấu '*'.
(58)dành
cho
hội
đồng
nghiệm
thu
50 Hàm
3
4 void PrintAsterik (int numAsteriks ) {
6 for (int count = 1; count <= numAsteriks ; count ++) cout << "*" ;
8 cout << endl; return ; 10 }
11
12 int main () 13 {
14 int edge_length ; // Chieu dai canh day cua tam giac 15 cout << " Enter the length of the base of an equilateral triangle : "; 16 cin >> edge_length ;
17 int count1 , count2 ;
18 for ( count1 = 1; count1 <= edge_length ; count1 += 2) 19 {
20 for ( count2 = 1; count2 <= ( edge_length - count1 )/2; count2 ++) 21 cout << " ";
22 PrintAsterik ( count1 ); 23 }
24
25 return 0; 26 }
Hình 4.3: Hàm in tam giác cân dấu '*'.
Do độ phân giải hình nên ta in dịng có số lẻ dấu '*', chương trình biến đếm count1 chạy từ sau bước tăng lên đơn vị
Ví dụ sau định nghĩa hàm tính luỹ thừa n (với n nguyên) số thực Hàm này có hai đầu vào (đối thực x số mũ nguyên n ) đầu (giá trị trả lại) kiểu thực với độ chính xác gấp đơi xn (Đây ví dụ minh họa hàm NSD tự xây dựng, C/C++ có sẵn hàm pow(x, y) để tính xy với x, y số thực).
1 double Power (double x, int n) {
3 double res = 1.0 ; // bien luu ket qua for (int count = 1; count <= n; count ++) res *= x ;
6 return res; // tra lai ket qua va ket thuc ham }
Hình 4.4: Hàm tính xn.
Để sử dụng hàm power, ví dụ cần tính giá trị biểu thức x4+ 2x3− 3x + ta viết thêm hàm main() sau:
1 # include <iostream > using namespace std;
4 double Power (double x, int n) { Installed above }
(59)dành
cho
hội
đồng
nghiệm
thu
4.2 Hàm 51
8 {
9 double x, res;
10 cout << " Enter value of x = "; cin >> x; 11 res = Power (x, 4) + 2* Power (x, 3) - 2*x + 1;
12 cout << " Value of x^4 + 2x^3 - 2x + is " << res << endl; 13
14 return 0; 15 }
Hình 4.5: Sử dụng hàm power().
4.2.3 Khai báo hàm
Về nguyên tắc, loại đối tượng (kiểu, hằng, biến, ) phải khai báo trước sử dụng, điều yêu cầu hàm Có nghĩa, hàm main() hàm B() có gọi đến hàm A() A() phải viết trước hàm main() hàm B() Tuy nhiên, chương trình lớn để che giấu bớt chi tiết ta cần khai báo A() trước main() hoặc B(), việc viết (định nghĩa, cài đặt) A() để sau.
1 # include <iostream > using namespace std;
4 double Power (double, int); // khai bao ham power ( truoc )
6 int main () {
8 cout << " Value of 5^2 is " << Power (5, 2) << endl; // main goi power return 0;
10 } 11
12 double Power (double x, int n) // cai dat ham power (sau) 13 {
14 double res = 1.0 ;
15 for (int count = 1; count <= n; count ++) 16 res *= x ;
17 return res; 18 }
Hình 4.6: Khai báo hàm trước gọi
Để khai báo hàm cần viết dòng tiêu đề hàm kết thúc dấu ';' Ngồi trong khai báo để tên đối khơng, ví dụ ta khai báo:
double power(double x, int n); hoặc double power (double, int);
Ví dụ :
int rand100 (); // khong doi , kieu ham la int
int square (int); // mot doi kieu int , kieu ham la int
void alltrim (char[]) ; // mot doi kieu xau , ham khong tra lai gia tri
int sum(int, int); // hai doi kieu int , kieu ham la int
(60)dành
cho
hội
đồng
nghiệm
thu
52 Hàm
• Danh sách đối khai báo hàm chứa khơng chứa tên đối, thơng thường ta khai báo kiểu đối không cần khai báo tên đối, dòng cài đặt (định nghĩa) hàm phải có tên đối đầy đủ
• Cuối khai báo hàm phải có dấu chấm phẩy ';', cuối dòng định nghĩa hàm khơng có dấu chấm phẩy
• Hàm khơng đối (danh sách đối rỗng), nhiên cặp dấu ngoặc sau tên hàm phải được viết Ví dụ PrintAsterisk(), lamtho(), vietgiaotrinh(), …
• Giả sử có hàm A(), B() cài đặt (định nghĩa) theo thứ tự A(), B() Khi B() được phép có lời gọi đến A() (vì A() biết trước B(), ngược lại A() không gọi được đến B() ( A() chưa biết B() ) Để A() gọi B(), cần phải khai báo B() trước cài đặt A(), cấu trúc đoạn chương trình sau:
Khai báo B;
Cài đặt A; // trong A có lời gọi đến B Cài đặt B; // trong B có lời gọi đến A
Một cách tự nhiên, NSD thường cài đặt hàm “con” trước main() cài đặt cuối cùng Tuy nhiên chương trình lớn, việc đặt main() cuối gây khó khăn cho việc tìm đến và đọc hiểu nội dung chương trình Vì vậy, hàm main() thường viết sau đến hàm khác Để làm việc này, cần có khai báo danh sách tất hàm lên đầu chương trình trước viết hàm main() (là nơi chứa nội dung chương trình).
Để “ẩn giấu” bớt chi tiết (không phải nội dung chính), việc khai báo biến, hằng, kiểu, hàm cài đặt hàm viết file khác Chương trình cần sử dụng đến đối tượng “ #include ” file khai báo vào đầu chương trình mình, sau sử dụng mà khơng cần quan tâm đến cách làm việc cụ thể đối tượng (ví dụ với hàm, NSD cần biết đầu vào, giá trị trả lại hàm (tức công dụng hàm) để sử dụng ngữ nghĩa chương trình mà khơng cần biết hàm làm việc nào) Chẳng hạn, NSD cần biết hàm sqrt(x) có cơng dụng tính bậc đối x, khai báo cài đặt sẵn trong file math.h, để sử dụng sqrt(x), cần đặt thêm câu khai báo #include <math.h> vào đầu chương trình
Tóm lại, chương trình lớn (để dễ đọc) thường phân chia thành file: file khai báo đối tượng, file cài đặt cụ thể hàm file chứa chương trình
4.3 Cách sử dụng hàm
4.3.1 Lời gọi hàm
Một hàm sau khai báo, cài đặt sử dụng thông qua “lời gọi hàm” Lời gọi hàm phép xuất biểu thức, câu lệnh cần đến Để gọi hàm, ta cần viết tên hàm danh sách giá trị thực để truyền cho đối
func_name ( list_of_values ) ;
(61)dành
cho
hội
đồng
nghiệm
thu
4.3 Cách sử dụng hàm 53
• list_of_values danh sách tham đối thực gọi danh sách giá trị gồm giá trị cụ thể (có thể biểu thức) để gán cho đối hình thức hàm Khi hàm gọi, việc chương trình tính tốn giá trị cụ thể, gán giá trị cho tham đối hình thức theo thứ tự tương ứng danh sách đối hàm, sau hàm tiến hành thực câu lệnh hàm (để tính kết quả)
• Danh sách tham đối thực truyền cho tham đối hình thức có số lượng với số lượng đối hàm truyền cho đối theo thứ tự tương ứng Các tham đối thực hằng, biến biểu thức Tên biến tham đối giá trị trùng với tên tham đối hình thức Ví dụ ta có hàm power(double x, int n); lời gọi hàm power(a+1, 4); thì x n đối hình thức, a+1 đối thực giá trị Các đối hình thức x n gán giá trị tương ứng x = a+1 n = trước khi tiến hành câu lệnh phần thân hàm Giả sử a biến trước nhận giá trị 2 thì lời gọi hàm power(a+1, 4) cho kết 34 = 81 Ngược lại, để tính 43 thì lời gọi hàm phải power(4, a+1).
• Các giá trị tương ứng truyền cho đối phải có kiểu với kiểu đối (hoặc C/C++ tự động chuyển kiểu đối)
• Khi hàm gọi, chương trình nơi gọi tạm thời chuyển điều khiển đến thực hàm gọi Sau kết thúc thực hàm, điều khiển lại trả thực tiếp câu lệnh sau lệnh gọi hàm nơi gọi
Ví dụ: Để tìm phân số tối giản ta cần cài đặt hàm tìm Ước chung lớn số sau sử dụng hàm để giản ước tử mẫu phân số chương trình sau
1 # include <iostream > using namespace std;
4 int GCD(int m, int n) // Ham tim UCLN cua m va n {
6 while (m != n)
7 {
8 if (m > n) m -= n; else n -= m;
10 }
11 return m; 12 }
13
14 int main () 15 {
16 int numerator , denominator , gcd; 17 cout << " Enter the numerator = "; 18 cin >> numerator ;
19 cout << " Enter the denominator = "; 20 cin >> denominator ;
21 gcd = GCD(numerator , denominator );
22 cout << " Fraction in its lowest terms = "
23 << numerator /gcd << "/" << denominator /gcd << endl; 24
(62)dành
cho
hội
đồng
nghiệm
thu
54 Hàm
26 }
Hình 4.7: Hàm tìm ước chung lớn
4.3.2 Hàm với đối mặc định
Trong phần trước, khẳng định số lượng tham đối thực phải số lượng tham đối hình thức gọi hàm Tuy nhiên, thực tế nhiều lần hàm gọi với giá trị số tham đối hình thức lặp lặp lại (cố định) Trong trường hợp lúc phải viết danh sách dài tham đối thực giống cho lần gọi công việc không thú vị Từ thực tế đó, C++ đưa cú pháp hàm cho danh sách tham đối thực lời gọi không thiết phải viết đầy đủ số chúng có sẵn giá trị định trước Cú pháp gọi hàm với tham đối mặc định khai báo với cú pháp sau:
type_name function_name (arg_1 , , arg_k ,
darg_1 = v_1 , , darg_m = v_m)
• Các đối arg_1, , arg_k đối mặc định darg_1, , darg_m khai báo cũ nghĩa gồm có kiểu đối tên đối
• Riêng đối mặc định darg_1, , darg_m có gán thêm giá trị mặc định v_1, , v_m. Một lời gọi gọi đến hàm phải có đầy đủ tham đối thực ứng với arg_1, , arg_k có khơng tham đối thực ứng với đối mặc định darg_1, , darg_m Nếu tham đối khơng có tham đối thực (trong lời gọi) thì tự động gán giá trị mặc định v_1, , v_m khai báo.
Ví dụ :
• Xét hàm double power(double x, int n = 2); Hàm có tham đối mặc định là số mũ n, lời gọi hàm bỏ qua số mũ chương trình hiểu tính bình phương x ( n = ) Ví dụ lời gọi power(4, 3) hiểu 43 cịn power(4) hiểu 42. • Hàm tính tổng số nguyên: int sum(int m, int n, int k = 0, int h = 0); có
thể tính tổng 5, 2, 3, lời gọi hàm sum(5, 2, 3, 7) tính tổng 3 số 4, 2, lời gọi sum(4, 2, 1) gọi sum(6, 4) để tính tổng của số 4.
1 # include <iostream > using namespace std;
4 int Sum(int m, int n, int k = 0, int h = 0) {
6 return m + n + k + h; }
8
9 int main () 10 {
(63)dành
cho
hội
đồng
nghiệm
thu
4.4 Biến toàn cục biến địa phương 55
12 cout << "4 + + equals to " << Sum (4, 2, 1) << endl; // 7 13 cout << "6 + equals to " << Sum (6, 4) << endl; // 10 14 return 0;
15 }
Hình 4.8: Hàm sum tính tổng số với tham số mặc định.
Chú ý: Các đối ngầm định phải khai báo liên tục xuất cuối danh sách đối Ví dụ:
int sum(int x, int y=2, int z, int t=1); // sai đối mặc định khơng liên tục
void sub(int x=0, int y); // sai đối mặc định khơng cuối
4.4 Biến tồn cục biến địa phương
4.4.1 Biến địa phương (biến hàm, khối lệnh)
Biến địa phương (còn gọi biến cục bộ) biến khai báo thân hàm và có tác dụng hàm (kể biến khai báo hàm main() có tác dụng riêng hàm main() ) Từ đó, tên biến hàm khác phép trùng nhau. Các biến hàm tồn thời gian hàm hoạt động Khi bắt đầu hoạt động, biến tự động sinh (trong nhớ) đến hàm kết thúc biến Tóm lại, hàm xem đơn vị độc lập, khép kín
Tham đối hàm xem biến cục Dưới ta nhắc lại chương trình nhỏ gồm hàm: luỹ thừa, xố hình main() Mục đích để minh hoạ biến cục bộ.
1 # include <iostream > using namespace std;
4 double Power (double x, int n) {
6 int i;
7 double res = 1;
8 for (i = 1; i <= n; i++) res *= x;
10 return res; 11 }
12
13 void ClearScreen (int n) // xoa man hinh n lan 14 {
15 int i; // i, n la cuc bo cua clearScreen 16 for (i = 1; i <= n; i++)
17 {
18 cout << " Press any key to clear screen (" << i << "th time)"; 19 cin.get (); // yeu cau doc vao phim bat ky 20 system ("CLS");
21 } 22 } 23
(64)dành
cho
hội
đồng
nghiệm
thu
56 Hàm
26 double x; int n; // x, n la cuc bo cua main
27 cout << " Enter x = "; cin >> x; 28 cout << " Enter n = "; cin >> n;
29 cin ignore (); // xoa ki tu luu bo dem ban phim
30 ClearScreen (3); // xoa man hinh lan
31 cout << " Result = " << Power (x, n) << endl; // in x ^ n 32 return 0;
33 }
Hình 4.9: Biến cục
Qua ví dụ ta thấy, biến count, đối n khai báo hai hàm: power() và clearScreen() Biến res khai báo power main(), biến x n của main() trùng tên với đối hàm power() Tuy nhiên, tất khai báo hợp lệ xem khác Có thể giải thích sau:
• Tất biến cục hàm khai báo
• x n main() có thời gian hoạt động dài nhất: Trong suốt trình chạy chương trình, chúng chương trình chấm dứt Đối x n power() tạm thời tạo hàm power() gọi đến độc lập với x, n main(), nói cách khác thời điểm nhớ có hai biến x hai biến n Khi hàm power() chạy xong biến x n tự động biến mất, x n main() tồn tại chạy hết chương trình
• Tương tự đối n, biến i power() clearScreen() độc lập với nhau, chúng tạo tồn thời gian hàm chúng gọi hoạt động Tức chúng biến khác nhau, không ảnh hưởng đến thời điểm hoạt động Việc đặt lại giá trị i hàm power() không làm thay đổi giá trị i clearScreen().
4.4.2 Biến toàn cục (biến tất hàm)
Biến tồn cục (cịn gọi biến toàn thể, biến dùng chung) biến khai báo bên tất hàm Vị trí khai báo chúng từ đầu văn chương trình một vị trí văn chương trình Thời gian tồn chúng từ lúc chương trình bắt đầu chạy đến kết thúc chương trình giống biến hàm main() Tuy nhiên, phạm vi tác dụng chúng điểm khai báo chúng hết chương trình, tức hàm khai báo sau sử dụng thay đổi giá trị chúng Như vậy, biến khai báo từ đầu chương trình có tác dụng lên tồn hàm chương trình Nếu x biến tồn cục khai báo từ đầu chương trình hàm A(), B(), C(), main() … bất kỳ phép truy cập đến thay đổi giá trị x.
4.4.3 Mức ưu tiên biến toàn cục địa phương
(65)dành
cho
hội
đồng
nghiệm
thu
4.4 Biến toàn cục biến địa phương 57
B() thấy biến x (toàn cục) với giá trị ) Trường hợp này, ta gọi che giấu biến (biến x tồn cục che giấu – khơng có tác dụng - A() )
Dưới ví dụ minh hoạ cho giải thích Ví dụ 1: Biến tồn cục dùng chung
Giả sử ta có trị chơi đơn giản sau: Đầu tiên, máy đưa giá trị nguyên ngẫu nhiên Sau đó, hai người chơi cộng thêm số (nguyên) vào kết trước Ai cho kết số chia chẵn cho người thắng trị chơi chấm dứt sau lần cộng thêm mà số chưa chia hết cho
Vì kết cộng thêm liên tục vào số nên chương trình dùng biến x chung (toàn thể) để chứa kết hàm cộng cho người chơi
1 # include <iostream > using namespace std;
4 int x;
6 void PlayerOne () {
8 int num;
9 cout << " First Player enters an integer : "; 10 cin >> num;
11 x += num; 12 }
13
14 void PlayerTwo () 15 {
16 int num;
17 cout << " Second Player enters an integer : "; 18 cin >> num;
19 x += num; 20 }
21
22 int main () 23 {
24 x = rand (); 25 int time = 1; 26 while (time <= 3) 27 {
28 PlayerOne (); 29 if (x % == 0)
30 {
31 cout << "Your obtained number is " << x << " You win" << endl;
32 break;
33 }
34 PlayerTwo (); 35 if (x % == 0)
36 {
37 cout << "Your obtained number is " << x << " You win" << endl;
38 break;
39 }
(66)dành
cho
hội
đồng
nghiệm
thu
58 Hàm
42 if (time > 3) cout << "Game Over\n"; 43 return 0;
44 }
Hình 4.10: Trị chơi cộng số để số chia hết cho năm Một vài tình huống:
• Nếu x khai báo main(), chương trình báo lỗi hàm khơng nhận biết x.
• Nếu khai báo main() hàm lời giải khơng với mục đích trị chơi vì người chơi số riêng
• Tương tự, x khai báo toàn thể hàm con, người chơi 2 biến khác : biến toàn thể biến cục khơng với u cầu trị chơi Ví dụ 2: Ảnh hưởng biến dùng chung.
Chúng ta xét lại hàm power() clearScreen() Chú ý hai hàm này đều có biến i làm biến đếm cho câu lệnh for, khai báo i lần như một biến (để dùng chung cho power() clearScreen() ), ngồi x được khai báo biến Cụ thể:
1 # include <iostream > using namespace std;
4 int i; // Khai bao bien i dung chung
5 double x; // Khai bao bien x dung chung
6 double Power (double x, int n); // Khai bao ham power
7 void ClearScreen (int n); // Khai bao ham clearScreen
9 int main () 10 {
11 x = 2; 12 i = 5;
13 ClearScreen (3); // xoa man hinh lan
14 cout << x << " ^ " << i << " = " << Power (x, i) << endl; // in ^ 5 15 return 0;
16 } 17
18 double Power (double x, int n) // Cai dat ham power 19 {
20 double res = 1;
21 for (i = 1; i <= n; i++) 22 res *= x;
23 return res; 24 }
25
26 void ClearScreen (int n) // Cai dat ham xoa man hinh 27 {
28 for (i = 1; i <= n; i++) 29 {
(67)dành
cho
hội
đồng
nghiệm
thu
4.4 Biến toàn cục biến địa phương 59
31 cin.get (); 32 system ("CLS"); 33 }
34 }
Hình 4.11: Ảnh hưởng biến dùng chung
Nhìn vào hàm main() ta thấy giá trị 25 được tính cách đặt x = 2, i = gọi hàm power(x, i) Kết ta mong muốn giá trị 32, nhiên không Trước khi in kết quả, hàm clearScreen(3) gọi để xố hình lần Hàm sử dụng biến ngoài i để làm biến đếm cho vịng lặp for sau khỏi for (cũng kết thúc clearScreen(3) ), i nhận giá trị Biến i lại sử dụng lời gọi power(x, i) hàm main(), tức thời điểm x = i = 4, kết in hình 24 = 16 thay 32 mong muốn.
Tóm lại, “điểm yếu” dẫn đến sai sót chương trình chỗ lập trình viên “tranh thủ” sử dụng biến i cho hàm clearScreen() main() (bằng cách khai báo biến ngồi) lại với mục đích khác Do sau chạy xong hàm clearScreen(), biến i bị thay đổi khác với giá trị i khởi tạo lúc ban đầu.
Để khắc phục lỗi chương trình trên, ta cần khai báo lại biến i : main() khai báo thêm i (nó che biến i ngồi), hai clearScreen() main() đều có biến i (cục hàm).
Từ đó, nên đề vài nguyên tắc biến dùng chung (toàn cục) biến cục bộ:
• Nếu biến sử dụng mục đích riêng hàm nên khai báo biến biến cục hàm Ví dụ biến đếm vịng lặp, thơng thường chúng sử dụng chí riêng vịng lặp chưa phải cho tồn hàm, khơng nên khai báo chúng biến Những biến cục sau hàm kết thúc chúng kết thúc, không gây ảnh hưởng đến hàm khác Một đặc điểm có lợi cho khai báo cục chúng tạo cho hàm tính cách hồn chỉnh, độc lập với hàm khác, chương trình khác Ví dụ hàm clearScreen() mang qua chạy chương trình khác mà khơng phải sửa chữa i khai báo bên hàm Trong ví dụ này, hàm clearScreen() vẫn hoạt động chương trình khác khơng có i biến ngồi (để clearScreen() sử dụng) hàm gây lỗi.
• Với biến mang tính chất sử dụng chung rõ nét (đặc biệt với biến kích thước lớn) mà nhiều hàm sử dụng chúng với mục đích giống nên khai báo chúng biến Điều tiết kiệm thời gian cho người lập trình khơng phải khai báo chúng nhiều lần nhiều hàm, tiết kiệm nhớ khơng phải tạo chúng tạm thời chạy hàm, tiết kiệm thời gian chạy chương trình khơng phải tổ chức nhớ để lưu trữ giải phóng chúng Ví dụ chương trình quản lý sinh viên, biến sinh viên dùng chung thống hầu hết hàm (xem, xố, sửa, bổ sung, thống kê ) nên khai báo chúng biến ngoài, điều tăng tính thống chương trình (mọi biến sinh viên cho hàm chương trình)
(68)dành
cho
hội
đồng
nghiệm
thu
60 Hàm
4.5 Tham đối chế truyền giá trị cho tham đối
Một hàm sau khai báo, định nghĩa phép sử dụng (gọi) đoạn chương trình khác để gọi đến hàm cần cung cấp (truyền) giá trị đầu vào cho danh sách tham đối Có cách truyền: Truyền thơng qua biến thường (truyền theo tham trị), truyền thông qua biến tham chiếu (tham chiếu) truyền thông qua biến trỏ (tham trỏ) Mục xét hai cách truyền theo tham trị tham chiếu Cách lại đề cập đến chương sau
4.5.1 Truyền theo tham trị
Ta xét lại ví dụ hàm power(double x, int n) để tính xn Giả sử chương trình chính ta có biến a, b, f chứa giá trị a = 2, b = 3, f chưa có giá trị Để tính ab và gán giá trị tính cho f, ta gọi f = power(a, b) Khi gặp lời gọi này, chương trình tổ chức sau:
• Tạo biến (tức ô nhớ nhớ) tương ứng với x n Gán nội dung ô nhớ này giá trị lời gọi, tức x nhận giá trị a (2) n nhận giá trị b (3). • Tới phần khai báo (của hàm), chương trình tạo thêm nhớ mang tên res i.
• Tiến hành tính toán với x = n = 3, gán kết cho res.
• Cuối lấy kết res gán cho ô nhớ f (là ô nhớ có sẵn khai báo trước, nằm bên ngồi hàm)
• Kết thúc, hàm quay chương trình gọi Do hàm power hồn thành xong việc tính tốn nên nhớ cục tạo thực hàm (gồm x, n, res, i ) xoá khỏi nhớ Kết tính tốn lưu giữ nhớ f (khơng bị xố khơng liên quan đến hàm – khai báo hàm)
Trên truyền đối theo cách thơng thường hay cịn gọi truyền theo tham trị Với cách truyền sau chạy xong hàm, giá trị biến bên khơng thay đổi (kể hàm có câu lệnh thay đổi đối) ví dụ sau:
1 # include <iostream > using namespace std;
4 double Total_Bit (int bytes , int bits) {
6 bits = 8* bytes + bits; return bits;
8 }
10 int main () 11 {
12 int bytes = 4, bits = 3;
13 cout << bytes << " bytes and " << bits << " bits equals to " << Total_Bit (bytes ,
bits) << " bits\n";
(69)dành
cho
hội
đồng
nghiệm
thu
4.5 Tham đối chế truyền giá trị cho tham đối 61
Hình 4.12: Tính số bít
Trong ví dụ ta cần tính tổng số bit byte bit thơng qua hàm Total_bit(). Hàm có đối bytes bits, hàm đối bits thay đổi giá trị (bằng 35) , nhiên biến bits hàm giữ giá trị cũ (3).
Tương tự, ta xem thêm ví dụ việc hốn đổi giá trị hai biến a = 3, b = sau. # include <iostream >
2 using namespace std;
4 void Swap(int x, int y) {
6 int temp ; temp = x ; x = y ; y = temp ;
10 cout << "x = " << x << ", y = " << y << endl; // 5, (x,y doi gia tri) 11 }
12
13 int main () 14 {
15 int a = 3, b = 5; 16 Swap(a, b) ;
17 cout << "a = " << a << ", b = " << b << endl; // 3, (a,b van nhu cu) 18 return 0;
19 }
Hình 4.13: Tráo đổi số
Màn hình gồm dịng kết Một dịng in giá trị x = 5, y = 3, câu lệnh in của hàm swap, tức tham đối x y thay đổi giá trị, nhiên dòng kết thứ hai (từ lệnh in hàm main) giá trị biến a, b cũ.
Tương tự giải thích lời gọi hàm power(), hình vẽ 4.14 minh hoạ cách làm việc hàm swap, trước, sau gọi hàm.
Như vậy, hàm swap cần viết lại cho việc thay đối giá trị không thực các biến tạm ( x, y ) mà phải thực thực biến ( a, b ) Để thực việc này, ta phải thông qua dạng biến gọi biến tham chiếu Hình ảnh ví dụ là ta cho đối x, y “tham chiếu” đến biến a, b, hiệu ứng việc thay đổi x, y thực chất làm thay đổi a, b.
4.5.2 Biến tham chiếu
Khai báo
(70)dành
cho
hội
đồng
nghiệm
thu
62 Hàm
Trước Trong Sau
3 a
5 b
3 a
5 b
3
3
3 đối x đối y temp
x=5 y=3
3 a
5 b
Các biến tạm x, y, temp bị xóa
sau chạy xong hàm
Hình 4.14: Các bước tráo đổi biến hàm swap.
Dưới cú pháp khai báo biến tham chiếu cho tham chiếu đến biến khác (đã có sẵn)
var_type & refrence_var_name = var_name ;
Cú pháp khai báo thêm dấu ( & ) trước tên biến cho phép tạo biến tham chiếu ( refrence_var_name ) cho tham chiếu đến biến tham chiếu ( var_name ) kiểu và phải khai báo từ trước Khi đó, biến tham chiếu cịn gọi bí danh biến tham chiếu
Chú ý: Trong khai báo biến tham chiếu, luôn phải cho biến tham chiếu đến biến (khơng có cú pháp khai báo tên biến tham chiếu mà không kèm theo khởi tạo) Ví dụ:
int hung , dung; // khai báo biến nguyên hùng, dũng
int &ti = hung; // khai báo biến tham chiếu tí, tham chiếu đến hùng
int &teo = dung; // khai báo biến tham chiếu tèo, tham chiếu đến dũng
Từ vị trí trở đi, việc sử dụng tên hùng, tí dũng, tèo Ví dụ:
hung = 3, dung = 5;
ti ++; teo ++; // tương đương hung ++; dung ++;
cout << << dung; // 6
Đặc trưng biến tham chiếu
(71)dành
cho
hội
đồng
nghiệm
thu
4.5 Tham đối chế truyền giá trị cho tham đối 63
Tương tự, dung++ làm tăng giá trị dung teo++ làm tăng giá trị dung chứ tăng giá trị teo Từ đó, khác biệt có ích sử dụng để truyền đối cho hàm ( teo ) với mục đích làm thay đổi nội dung biến ( dung ).
4.5.3 Truyền theo tham chiếu
Đối hàm biến tham chiếu, việc thay đổi đối thực chất thay đổi biến ngồi mà tham chiếu Các biến ngồi truyền cho đối tham chiếu lời gọi hàm Cách truyền gọi truyền theo tham chiếu
Ví dụ, hàm swap viết lại để thay đổi giá trị cặp số a, b Khi đó, thay cho x, y đối thường ví dụ trước, x, y đối tham chiếu Các phần cịn lại (nội dung hàm, lời gọi hàm) khơng có thay đổi
1 # include <iostream > using namespace std;
4 void Swap(int &x, int &y) {
6 int temp ; temp = x ; x = y ; y = temp ;
10 cout << "x = " << x << ", y = " << y << endl; // 5, (x,y doi gia tri) 11 }
12
13 int main () 14 {
15 int a = 3, b = 5; 16 Swap(a, b) ;
17 cout << "a = " << a << ", b = " << b << endl; // 5, (a,b doi gia tri) 18 return 0;
19 }
Hình 4.15: Tráo đổi số tham chiếu Dưới bảng khác biệt hàm swap
Bảng 4.16: Khác biệt truyền tham số theo tham trị tham chiếu
Tham trị Tham chiếu
Khai báo đối void swap(int x, int y) void swap(int &x, int &y) Câu lệnh t = x; x = y; y = t; t = x; x = y; y = t;
Lời gọi swap(a, b); swap(a, b);
Tác dụng a, b khơng thay đổi a, b có thay đổi
(72)dành
cho
hội
đồng
nghiệm
thu
64 Hàm
trả lại bé ta khai báo đối tham chiếu để nhận giá trị (trường hợp giá trị trả lại tập liệu bàn đến chương nói mảng cấu trúc)
Ví dụ hàm giải phương trình bậc Hàm có đối biến thường đại diện cho hệ số a, b, c phương trình Vì hàm trả lại giá trị phương trình có nghiệm Do danh sách đối hàm cần thêm đối tham chiếu để nhận giá trị của nghiệm x1, x2 Khi đó, hàm khơng trả lại giá trị ( void) trả lại giá trị số lượng nghiệm phương trình (0, 2) (xem phần tập)
4.5.4 Hai cách truyền giá trị cho hàm từ khóa const
Qua hai cách truyền theo tham trị tham chiếu, rút nhận xét cách hoạt động hàm có lời gọi:
• Truyền theo tham trị : Đầu tiên, chương trình tạo nhớ đại diện cho đối hàm, ô nhớ lấp đầy giá trị cách chép giá trị biến (hoặc biểu thức) tương ứng lời gọi (thao tác chép thêm thời gian, đặc biệt biến có nội dung lớn) Tiếp theo, hàm thực tính tốn giá trị chép (bản sao) cuối trả lại giá trị kết
• Truyền theo tham chiếu: Mọi tính tốn biến tham chiếu thực chất biến ngồi có sẵn Hàm không thêm không gian (tạo ô nhớ tạm cho đối) không thêm thời gian (sao chép biến) Như vậy, cách viết hàm dạng đối tham chiếu có ưu điểm đối thường, chỗ:
– Cho phép thay đổi giá trị biến ngồi.
– Cải thiện tính hiệu thuật tốn (tốn nhớ, chạy nhanh hơn).
Từ đó, liên quan đến biến có nội dung lớn ta thường lợi dụng đối tham chiếu để tăng hiệu (tiết kiệm không gian, giảm thiểu thời gian thực hiện), nhiên, cách viết vô tình cho phép thay đổi giá trị biến ngồi mà ta không mong muốn Để ngăn cản việc thay đổi, C++ cho phép thêm từ khóa const trước đối tham chiếu Khi đó, hàm có câu lệnh vơ tình thay đổi giá trị đối (được khai báo sau từ khóa const), chương trình dịch báo lỗi
4.6 Ngăn xếp gọi hàm mẫu tin kích hoạt
(73)dành
cho
hội
đồng
nghiệm
thu
4.6 Ngăn xếp gọi hàm mẫu tin kích hoạt 65
Hình 4.17: Gọi stack sau hệ điều hành gọi hàm main để thực thi chương trình
hàm Giải thích hành vi vào sau trước ngăn xếp (LIFO) với ví dụ xếp chồng đĩa lên Như hình 4.17 - 4.19, LIFO cách hàm thực trả hàm gọi
Theo hàm gọi, trình trả về, gọi hàm khác - tất thực trước hàm trả Mỗi hàm phải trả lại quyền điều khiển hàm gọi Theo cách vậy, phải giữ địa trả nơi hàm cần trả lại quyền điều khiển hàm gọi Ngăn xếp cấu trúc liệu hoàn hảo để xử lý thông tin Mỗi lần hàm gọi hàm khác, đối tượng đẩy vào ngăn xếp Đối tượng gọi khung ngăn xếp mẫu tin kích hoạt, chứa địa trả mà hàm gọi cần để trả hàm gọi Nó chứa thêm số thông tin thảo luận Nếu hàm gọi trả về, thay gọi hàm khác trước trở về, mẫu tin kích hoạt cho việc gọi hàm chuẩn bị, điều khiển chuyển tới địa trả mẫu tin kích hoạt lấy
Thuận tiện ngăn xếp lời gọi hàm hàm gọi, ln có thông tin để trả đỉnh ngăn xếp gọi Nếu hàm gọi hàm khác, mẫu tin kích hoạt cho việc gọi hàm đơn giản đẩy vào ngăn xếp gọi hàm Do đó, địa trả yêu cầu hàm gọi trả lời gọi nằm đỉnh ngăn xếp
(74)dành
cho
hội
đồng
nghiệm
thu
66 Hàm
Ngăn xếp lời gọi hàm hoạt động
Hình 4.18: Gọi stack sau hàm main gọi hàm square để thực phép tính.
(75)dành
cho
hội
đồng
nghiệm
thu
4.6 Ngăn xếp gọi hàm mẫu tin kích hoạt 67
Như biết, ngăn xếp lời gọi hàm mẫu tin kích hoạt hỗ trợ chế gọi hàm/trả hàm, khởi tạo hủy biến tự động Theo dõi cách thức gọi ngăn xếp hỗ trợ hoạt động hàm square gọi hàm main (dịng 6-11 hình 4.20) Trước tiên, hệ điều hành gọi hàm main – việc đẩy mẫu tin kích hoạt vào ngăn xếp (trong hình 4.17) Mẫu tin kích hoạt cho hàm main biết làm để trả hệ điều hành (ví dụ chuyển đến địa trả R1) chứa không gian cho biến tự động hàm main (nghĩa khởi tạo đến 10) Hàm maintrước trở lại hệ điều hành -gọi hàm square dịng 10 hình 4.20 Điều dẫn đến mẫu tin kích hoạt cho hàm square (dịng 14-17) để đẩy vào ngăn xếp lời gọi hàm (Hình 4.18) Mẫu tin kích hoạt chứa địa trả mà hàm square cần trả lại hàm main (R2) nhớ cho biến tự động square (x)
Sau square tính bình phương tham số, cần trả hàm main - khơng cần nhiều nhớ cho biến tự động x Vì vậy, ngăn xếp lấy – đưa bình phương giá trị trả hàm main (R2) biến tự động square Hình 4.19 trình bày ngăn xếp lời gọi hàm sau mẫu tin kích hoạt hàm square lấy
Hàm main lúc hiển thị kết gọi hàm square (dịng 13) Chạm tới dấu ngoặc móc bên phải hàm main dẫn đến mẫu tin kích hoạt lấy từ ngăn xếp đưa tới địa trả hàm main hệ điều hành (R1 hình 4.20) dẫn đến nhớ cho biến tự động hàm main (A) trở nên khơng có sẵn
1 # include <iostream > using namespace std;
4 int square ( int ); // prototype for function square
6 int main () {
8 int a = 10; // value to square ( local automatic variable in main)
10 cout << a << " squared : " << square (a) << endl; // display a squared 11 } // end main
12
13 // returns the square of an integer
14 int square ( int x ) // x is a local variable 15 {
16 return x * x; // calculate square and return result 17 } // end function square
Hình 4.20: Hàm square dùng để minh họa gọi hàm stack kích hoạt mẫu tin
Output chương trình Hình 4.20:
10 squared : 100
(76)dành
cho
hội
đồng
nghiệm
thu
68 Hàm
4.7 Chồng hàm khuôn mẫu hàm
4.7.1 Chồng hàm (hàm trùng tên)
Hàm trùng tên hay gọi hàm chồng (đè) Đây kỹ thuật cho phép sử dụng tên gọi cho hàm giống (cùng mục đích) xử lý kiểu liệu khác số lượng liệu khác Ví dụ hàm sau tìm số lớn số nguyên:
int max(int a, int b) { return (a > b) ? a: b ; }
Nếu đặt c = max(3, 5) ta có c = Tuy nhiên tương tự đặt c = max(3.0, 5.0) chương trình bị lỗi giá trị (double 3.0, 5.0) khơng phù hợp kiểu ( int ) đối hàm max Trong trường hợp vậy, phải viết hàm để tính max số thực Mục đích cách hoạt động hàm hồn toàn giống hàm trước, nhiên C NNLT cổ điển khác buộc phải sử dụng tên cho hàm “mới” Ví dụ:
double fmax(double a, double b) { return (a > b) ? a: b ; }
// Tương tự để thuận tiện ta viết thêm hàm
char cmax(char a, char b) { return (a > b) ? a: b ; }
long lmax(long a, long b) { return (a > b) ? a: b ; }
double dmax(double a, double b) { return (a > b) ? a: b ; }
Tóm lại, ta có hàm: max, cmax, fmax, lmax, dmax để làm công việc, việc sử dụng tên gây bất lợi cần gọi hàm
Để khắc phục, C++ cho phép ta khai báo định nghĩa hàm với tên gọi ví dụ max chẳng hạn Khi đó, ta có hàm:
1: int max(int a, int b) { return (a > b) ? a: b ; }
2: double max(double a, double b) { return (a > b) ? a: b ; } 3: char max(char a, char b) { return (a > b) ? a: b ; }
4: long max(long a, long b) { return (a > b) ? a: b ; }
5: double max(double a, double b) { return (a > b) ? a: b ; }
Và lời gọi hàm dạng max(3, 5), max(3.0, 5), max('O', 'K') đáp ứng Chúng ta đặt vấn đề: với hàm tên vậy, chương trình gọi đến hàm Vấn đề giải dễ dàng chương trình dựa vào kiểu đối gọi để định chạy hàm Ví dụ lời gọi max(3, 5) có đối kiểu nguyên nên chương trình gọi hàm 1, lời gọi max(3.0, 5) hướng đến hàm số tương tự chương trình chạy hàm số khi gặp lời gọi max('O', 'K') Như đặc điểm hai hàm trùng tên danh sách đối chúng phải có cặp đối khác kiểu Một đặc trưng khác để phân biệt hai hàm trùng tên số lượng đối hàm phải khác (nếu kiểu chúng giống nhau)
Ví dụ việc vẽ hình: thẳng, tam giác, vng, chữ nhật hình giống nhau, chúng phụ thuộc vào số lượng điểm nối toạ độ điểm Do vậy, ta khai báo định nghĩa hàm vẽ nói với chung tên gọi Chẳng hạn:
// vẽ đường thẳng AB
void draw( Point_Type A, Point_Type B) ;
// vẽ tam giác ABC
(77)dành
cho
hội
đồng
nghiệm
thu
4.7 Chồng hàm khuôn mẫu hàm 69
// vẽ tứ giác ABCD
void draw( Point_Type A, Point_Type B, Point_Type C, Point_Type D) ;
Trong ví dụ trên, ta giả thiết Point_Type kiểu liệu lưu toạ độ điểm màn hình Hàm draw(Point_Type A, Point_Type B, Point_Type C, Point_Type D) vẽ hình vng, chữ nhật, thoi, bình hành hay hình thang phụ thuộc vào toạ độ điểm A, B, C, D, nói chung sử dụng để vẽ tứ giác
Tóm lại, nhiều hàm định nghĩa chồng (với tên gọi giống nhau) chúng thoả điều kiện sau:
• Số lượng tham đối hàm khác • Kiểu tham đối hàm khác
(Kỹ thuật chồng tên cịn áp dụng cho tốn tử (phép tốn) trình bày chương sau)
1 # include <iostream > using namespace std;
4 // Ham tim UCLN cua doi int GCD(int m, int n) {
7 while (m != n)
8 {
9 if (m > n) m -= n; 10 else n -= m;
11 }
12 return m; 13 }
14
15 // Ham tim UCLN cua doi ( trung ten voi ham UCLN doi) 16 int GCD(int m, int n, int k)
17 {
18 return GCD(GCD(m, n), k); 19 }
20
21 int main () 22 {
23 int num1 , num2 , num3;
24 cout << " Enter three numbers : " ; 25 cin >> num1 >> num2 >> num3 ;
26 cout << "GCD of num1 and num2 is " << GCD(num1 , num2) << endl; 27 cout << "GCD of num1 and num3 is " << GCD(num1 , num3) << endl;
28 cout << "GCD of three numbers is " << GCD(num1 , num2 , num3) << endl; 29 return 0;
30 }
Hình 4.21: Nạp chồng hàm tính ước số chung lớn
Chú ý: Cần tránh tính nhập nhằng hàm với đối mặc định hàm trùng tên Xét ví dụ sau, cho hàm với đối mặc định :
(78)dành
cho
hội
đồng
nghiệm
thu
70 Hàm
Và hàm trùng tên với hàm trên:
2. int sum(int a, int b);
• Các hàm khai báo cài đặt hợp lệ (tuy có tên số lượng đối khác nhau)
• Nếu gọi sum(1, 2, 3, 4) sum(1, 2, 3), chương trình gọi hàm 1.
• Tuy nhiên, gọi sum(4, 7), chương trình không định nên gọi hàm hay hàm 2, trường hợp chương trình dịch báo lỗi
4.7.2 Khuôn mẫu hàm
Việc cho phép hàm trùng tên tạo dễ dàng cho NSD gọi đến hàm với tên gọi Tuy nhiên, lập trình cần phải viết tất hàm vậy, điều dẫn đến lãng phí cơng sức hàm có chung cung cách hoạt động Ta xét lại ví dụ mục trước cần viết hàm tìm số lớn hai số, để phủ đầy đủ loại kiểu ta cần viết nhiều hàm có tên max sau:
1: int max(int a, int b) { return (a > b) ? a: b ; }
2: double max(double a, double b) { return (a > b) ? a: b ; } 3: char max(char a, char b) { return (a > b) ? a: b ; }
4: long max(long a, long b) { return (a > b) ? a: b ; }
5: double max(double a, double b) { return (a > b) ? a: b ; } 6:
Rõ ràng hàm có cách viết, cách hoạt động, tên gọi, điểm khác biệt chỗ kiểu đầu vào đầu Do vậy, có lẽ ta nên viết hàm để “dùng chung” cho tất ? có nghĩa kiểu đối và/hoặc kiểu hàm xem tham đối ? Điều thực kỹ thuật “khuôn mẫu hàm” C++ cung cấp, tức cho phép viết chung hàm “mẫu” dùng với kiểu liệu khác
Chìa khóa cho việc dùng chung khai báo kiểu chung với tên gọi đó, câu lệnh
template <typename identifier > // identifier : tên kiểu chung
hoặc
template <class identifier > // (các từ typename class tương đương)
và hàm chỗ liên quan đến kiểu ta dùng tên chung identifier thay cho tên kiểu cụ thể Trong phần gọi hàm, C++ tự động thay kiểu cụ thể vào tên kiểu chung tiến hành thực hàm cách bình thường Dưới ví dụ minh họa cho cách viết hàm mẫu getMax dùng chung cho hàm tìm max liệt kê
1 # include <iostream > using namespace std;
4 // GenType la ten kieu chung dai dien cho tat ca cac kieu template <typename Gen_Type >
(79)dành
cho
hội
đồng
nghiệm
thu
4.7 Chồng hàm khuôn mẫu hàm 71
8 return (a > b ? a : b); }
10
11 int main () 12 {
13 int m, n; 14 double x, y; 15 char c, d;
16 cout << " Enter integrs m, n: " ; cin >> m >> n;
17 cout << " Maximun of two integars " << m << " and " << n << " is: " << getMax (m, n
) << endl;
18 cout << " Enter doubles x, y: "; cin >> x >> y;
19 cout << " Maximun of two doubles " << x << " and " << y << " is: " << getMax (x, y)
<< endl;
20 cout << " Enter characters c, d: "; cin >> c >> d;
21 cout << " Maximun of two characters " << c << " and " << d << " is: " << getMax (c,
d) << endl;
22 return 0; 23 }
Hình 4.22: Khn mẫu hàm getMax(). Để rõ ràng (đối với NSD), lời gọi hàm ta viết:
getMax <int> (m, n);
getMax <double> (x, y);
Tuy nhiên, để ngắn gọn ta cần viết getMax(m, n), getMax(x, y) ví dụ, C++ tự động nhận biết kiểu tham đối để thực
Ngoài việc tham đối có chung kiểu mẫu ví dụ trên, ta viết hàm với tham đối khác kiểu (ví dụ tìm max số thực số nguyên), ta cần khai báo thêm kiểu khai báo template tương ứng với tham đối
Ví dụ sau minh họa hàm tìm giá trị lớn giá trị khác kiểu (chú ý: giá trị phải phép đổi kiểu tự động cần thiết) trả lại giá trị theo kiểu thứ
1 # include <iostream > using namespace std;
4 template <typename T, typename U> // kieu mau cho doi khac kieu nhau
5 T GetMax (T a, U b) // gia tri tra lai la kieu cua doi thu (T) {
7 T res;
8 res = (a > b ? a : b); return res;
10 } 11
12 int main () 13 {
14 double double_num ; 15 int integer_num ; 16 char character ;
17 cout << " Enter the double , the integer , the character : " ; cin >> double_num >>
(80)dành
cho
hội
đồng
nghiệm
thu
72 Hàm
18 cout << " Maximum of " << double_num << " and " << integer_num << " is: " <<
GetMax ( double_num , integer_num ) << endl; // thuc - nguyen , tra lai thuc
19 cout << " Maximum of " << integer_num << " and " << character << " is: " << GetMax
( integer_num , character ) << endl; // nguyen - ki tu , tra lai nguyen
20 cout << " Maximum of " << integer_num << " and " << character << " is: " << GetMax
(character , integer_num ) << endl; // nguyen - ki tu , tra lai ki tu
21 return 0; 22 }
Hình 4.23: Khn mẫu hàm getMax() với kiểu đối số khác nhau.
Trong ví dụ trên, giả sử ta nhập giá trị tương ứng với double_num, integer_num, character là 64.5, 65, 'B' Khi đó, lời gọi getMax(double_num, integer_num) phép so sánh giá trị thực (64.5) nguyên (65) giá trị trả lại số thực ( 65.0 ) (C/C++ tự động đổi giá trị nguyên sang thực) Tương tự, lời gọi getMax(integer_num, character) trả lại số nguyên 66, lời gọi getMax(character, integer_num) trả lại kí tự 'B' Tuy nhiên, lời gọi getMax(integer_num, double_num) khơng phép giá trị trả lại kiểu nguyên mà kiểu thực chuyển kiểu tự động kiểu nguyên
4.8 Lập trình với hàm đệ quy
4.8.1 Khái niệm đệ qui
Một hàm gọi đến hàm khác bình thường, hàm lại gọi đến ta gọi hàm đệ qui Như vậy, hàm gọi đến hàm đệ qui hàm đệ qui chạy nhiều lần, chạy đến vô hạn lần, nhiên giống không tồn động vĩnh cửu, hàm đệ qui chạy đến “hết nhiên liệu” dừng
Để minh hoạ, ta xét hàm tính n giai thừa Để tính n! ta xây dựng hàm factorial() dùng phương pháp lặp sau:
1 # include <iostream > using namespace std;
4 long Factorial (int n) {
6 long res; res = 1;
8 for (int count = 1; count <= n; count ++) res *= count ;
10 return res; 11 }
12
13 int main () 14 {
15 int n;
16 cout << "n = " ; cin >> n; // nhap so can tinh giai thua 17 cout << n << "! = " << Factorial (n) << endl;
18 return 0; 19 }
(81)dành
cho
hội
đồng
nghiệm
thu
4.8 Lập trình với hàm đệ quy 73
Mặt khác, n! tính thơng qua (n− 1)! công thức truy hồi
n! =
1 nếu n = 0
(n− 1)! × n nếu n > 0.
Để tính giai thừa (của n ) ta lại thơng qua tính giai thừa số nhỏ ( n-1 ), ta có thể xây dựng hàm factorial() (đệ qui) tính n! sau:
1 # include <iostream > using namespace std;
4 long Factorial2 (int n) {
6 if (n == 0) return 1;
7 else return Factorial2 (n - 1) * n; }
9
10 int main () 11 {
12 int n;
13 cout << "n = " ; cin >> n; // nhap so can tinh giai thua 14 cout << n << "! = " << Factorial2 (n) << endl;
15 return 0; 16 }
Hình 4.25: Tính giai thừa hàm đệ qui
Hàm factorial(n) cài đặt hàm đệ qui hàm có gọi đến nó, cụ thể để tính factorial(n), hàm gọi đến factorial(n-1) với điểm khác biệt đối lần gọi sau là n-1, bé đối lần gọi trước n Hiển nhiên, cách này, để giải factorial(n-1) hàm lại phải gọi đến factorial(n-2), … q trình gọi đến tiếp tục với đối hàm mỗi lúc bé dần đến đối n giảm đến Do câu lệnh if (n == 0) return 1; hàm trả lại giá trị cho factorial(0) thay tiếp tục gọi đến Từ đây, q trình tính tốn ngược
được bắt đầu với factorial(1) = factorial(0) * = 1, factorial(2) = factorial(1) * = 1.2, factorial(3) = factorial(2) * = 1.2.3, … factorial(n) tính cuối 1.2.3 n, tức factorial(n) bằng
n!.
Ví dụ, hàm main() giả sử ta nhập 3 cho n, để thực câu lệnh cout << factorial(n) để in 3!, chương trình gọi chạy hàm factorial(3) Do 3 > nên hàm factorial(3) trả lại giá trị factorial(2) * 3, tức lại gọi hàm factorial với tham đối thực bước n = Tương tự, factorial(2) = factorial(1) * và factorial(1) = factorial(0) * Khi thực factorial(0) ta có đối n = nên hàm trả lại giá trị 1, từ factorial(1) = * = suy ngược trở lại ta có
factorial(2) = factorial(1) * = * = 2, factorial(3) = factorial(2) * = * = 6. Chương trình in kết 6.
Có thể biểu thị cách hoạt động sơ đồ sau (để gọn, sơ đồ ta dùng tên gọi gt để thay cho factorial ) :
(82)dành
cho
hội
đồng
nghiệm
thu
74 Hàm
main()
in gt(3)
= gt(2)*3 gọi gt(3)
= gt(1)*2 gọi gt(2)
= gt(0)*1 gọi gt(1)
= gọi gt(0)
in
trả lại gt(2)*3 = 2*3 = cho gt(3)
trả lại gt(1)*2 = 1*2 = cho gt(2)
trả lại gt(0)*1 = 1*1 = cho gt(1)
trả lại cho gt(0)
Hình 4.26: Quá trình gọi đệ quy hàm factorial • Việc thực gọi hàm nhiều lần gây thời gian
• Mỗi lần gọi chương trình tạo nên tập biến cục ngăn xếp (các đối, biến riêng khai báo hàm, địa câu lệnh sau chạy xong hàm ) độc lập với lần chạy trước, từ dễ gây tràn ngăn xếp (quá tải nhớ)
Mặc dù chiếm nhiều thời gian (chạy chương trình) khơng gian (bộ nhớ máy tính), đệ qui cách viết gọn, dễ viết đọc chương trình, mặt khác có nhiều tốn tìm thuật tốn lặp cho phức tạp viết theo thuật tốn đệ qui lại dễ dàng
Chú ý: Để dùng phương pháp đệ qui, ta phải viết riêng hàm cho mục đích Ví dụ để tính giai thừa phương pháp lặp ta cần viết hàm main() Để viết theo đệ qui, ta phải viết hàm factorial riêng, độc lập với hàm main().
4.8.2 Lớp toán giải đệ qui
Phương pháp đệ qui thường dùng để giải tốn có đặc điểm:
• Giải dễ dàng trường hợp riêng gọi trường hợp suy biến hay sở, trường hợp hàm tính bình thường mà khơng cần gọi lại (ví dụ trường hợp n = tốn tính n!).
(83)dành
cho
hội
đồng
nghiệm
thu
4.8 Lập trình với hàm đệ quy 75
Trong trường hợp tính n! n = hàm cho giá trị mà khơng cần phải gọi lại nó, đây trường hợp suy biến Trường hợp n > , hàm gọi lại với n giảm 1 đơn vị Việc gọi lặp lại n = 0.
Một lớp rộng toán dạng tốn định nghĩa dạng đệ qui (cịn gọi truy hồi) tốn lặp với số bước hữu hạn biết trước hay toán UCLN, tháp Hà Nội, Việc đưa định nghĩa toán dạng đệ qui làm dễ dàng cho cơng tác thiết kế thuật tốn từ viết hàm đệ qui cho tốn
Ví dụ: Gọi S(n) tổng n số tự nhiên đầu tiên, tức S(n) = + + + + n Tương tự bài tốn tính tích n số tự nhiên (tức n!), S(n) định nghĩa dạng đệ qui như sau:
S(n) =
1 nếu n = (trường hợp suy biến) S(n− 1) + n nếu n > (trường hợp tổng quát) . Từ đó, ta có chương trình:
1 # include <iostream > using namespace std;
4 int Sum(int n) {
6 if (n == 1) return 1;
7 else return Sum(n - 1) + n; }
9
10 int main () 11 {
12 int n;
13 cout << " Enter n = " ; cin >> n; // nhap so can tinh tong 14 cout << "Sum of first n integer numbers = " << Sum(n) << endl; 15 return 0;
16 }
Hình 4.27: Tính giai thừa hàm đệ qui
4.8.3 Cấu trúc chung hàm đệ qui
Dạng thức chung hàm đệ qui thường sau: if (tham đối suy biến)
trình bày cách giải trực tiếp // giả định có cách giải
else // tham đối chưa suy biến
gọi lại hàm với tham đối "bé" hơn
Một số ví dụ 1 Tính S(n) =
√ +
√
3 +√3 + +√3 với n dấu căn.
(84)dành
cho
hội
đồng
nghiệm
thu
76 Hàm
sau:
S(n) =
√
3 nếu n = 1
√
3 + S(n− 1) nếu n > 1. Từ đó, ta có chương trình đệ qui:
1 # include <iostream > # include <math.h> using namespace std;
5 double S(int numSquare_roots ) {
7 double res;
8 if ( numSquare_roots == 1) res = sqrt (3); else res = sqrt (3 + S( numSquare_roots - 1)); 10 return res;
11 } 12
13 int main () 14 {
15 int numSquare_roots ;
16 cout << " Number of the Square roots = " ; cin >> numSquare_roots ; 17 cout << " Result = " << S( numSquare_roots ) << endl;
18 return 0; 19 }
Hình 4.28: Tính S(n) với dấu lồng nhau. 2 Tính S(n) =
2+ 2+
2+ + 1
với n dấu chia Dạng định nghĩa đệ qui S(n) sau:
S(n) =
1/2 nếu n = 1
1/(2 + S(n− 1)) nếu n > 1.
Từ ta có chương trình đệ qui: # include <iostream >
2 # include <math.h> using namespace std;
5 double S(int numDivisions ) {
7 if ( numDivisions == 1) return 1/2.0; // chu y duoc viet 2.0 else return 1/(2 + S( numDivisions - 1));
9 } 10
11 int main () 12 {
13 int numDivisions ;
14 cout << " Number of the Divisions = " ; cin >> numDivisions ; 15 cout << "S = " << S( numDivisions ) << endl;
(85)dành
cho
hội
đồng
nghiệm
thu
4.8 Lập trình với hàm đệ quy 77
17 }
Hình 4.29: Tính S(n) với n dấu chia.
3 Dãy Fibonacci dãy f (n) định nghĩa : hai số có giá trị f (1) = f (2) = 1, số còn lại tổng giá trị số kề trước nó, tức f (n) = f (n− 1) + f(n − 2)(n > 2) Định nghĩa đệ qui dãy số viết:
f (n) =
1 nếu n = 1, 2
f (n− 1) + f(n − 2) nếu n > 1 . Từ đó, ta có chương trình đệ qui:
1 # include <iostream > using namespace std;
4 long Fib(int n) {
6 long res;
7 if (n==1 || n==2) res = 1; else res = fib(n -1) + fib(n -2); return res;
10 } 11
12 int main () 13 {
14 int n;
15 cout << "n = " ; cin >> n;
16 cout << "The first " << n << " fibonaci numbers \n"; 17 for (int count = 1; count <= n; count ++)
18 cout << Fib(count ) << endl; 19 return 0;
20 }
Hình 4.30: Tính số Fibonacci
4 Tìm UCLN số a, b Có thể định nghĩa hàm GCD(int m, int n) dạng đệ qui như sau:
• Nếu a = b GCD(a, b) = a
• Nếu a > b GCD(a, b) = GCD(a-b, b) • Nếu a < b GCD(a, b) = GCD(a, b-a)
Từ ta có chương trình đệ qui để tính UCLN a b: # include <iostream >
2 using namespace std;
4 int GCD(int m, int n) {
6 if (m == n) return m;
(86)dành
cho
hội
đồng
nghiệm
thu
78 Hàm
8 if (m < n) return GCD(m, n - m); }
10
11 int main () 12 {
13 int m, n;
14 cout << "m = " ; cin >> m; 15 cout << "n = " ; cin >> n;
16 cout << " Greatest common devisor of " << m << " and " << n << " is " << GCD(
m,n) << endl;
17 return 0; 18 }
Hình 4.31: Tính UCLN hàm đệ qui
5 Chuyển tháp toán cổ tiếng, nội dung sau: Cho tháp n tầng, xếp tại vị trí Yêu cầu tốn chuyển tồn tháp sang vị trí (cho phép sử dụng vị trí trung gian 3) theo điều kiện sau đây:
• Mỗi lần chuyển tầng tháp,
• Tại thời điểm, vị trí, tầng tháp lớn phải nằm tầng tháp nhỏ
Bài toán chuyển tháp với tầng minh hoạ hình vẽ bên
1 2 3
Trước chuyển
1 2 3
Sau chuyển
Hình 4.32: Bài tốn Tháp Hà Nội
Có thể tổng qt hóa tốn sau: chuyển n tầng tháp từ vị trí source đến vị trí target, source, target tham số lấy giá trị 1, 2, thể hiện cho vị trí Đối với vị trí source target, dễ thấy vị trí trung gian temp (vị trí cịn lại) vị trí 6-source-target (vì source + target + temp = 1+2+3 = ) Từ đó để chuyển tháp từ vị trí source đến vị trí target, ta xây dựng cách chuyển đệ qui sau:
(87)dành
cho
hội
đồng
nghiệm
thu
4.8 Lập trình với hàm đệ quy 79
• Chuyển trả n-1 tầng từ temp lại vị trí target
Hiển nhiên số tầng ta phải thực phép chuyển từ source sang target.
Mỗi lần chuyển tầng từ vị trí i đến j ta in i -> j Chương trình nhập vào input số tầng in bước chuyển theo kí hiệu Như vậy, kết chương trình một dãy bước chuyển dạng i -> j.
1 # include <iostream > using namespace std;
4 void Transpose (int source , int target ) {
6 cout << source << " -> " << target << endl; return;
8 }
10 void TransposeTower (int numfloors , int source , int target ) 11 {
12 int temp;
13 if ( numfloors == 1) Transpose (source , target ); 14 else
15 {
16 temp = - source - target ;
17 TransposeTower (numfloors -1, source , temp); 18 Transpose (source , target );
19 TransposeTower (numfloors -1, temp , target ); 20 }
21 return; 22 }
23
24 int main () 25 {
26 int num_floors ;
27 cout << " Enter the number of floors : " ; cin >> num_floors ; 28 TransposeTower ( num_floors , 1, 2);
29 return 0; 30 }
Hình 4.33: Bài tốn chuyển tháp
(88)dành
cho
hội
đồng
nghiệm
thu
80 Hàm
Bài tập
1 Chọn câu sai câu sau đây:
(a) Hàm không trả lại giá trị khơng cần khai báo kiểu giá trị hàm
(b) Các biến khai báo hàm cục bộ, tự xoá hàm thực xong (c) Hàm đơn vị độc lập, không khai báo hàm lồng
(d) Được phép gọi hàm thân hàm khác Chọn câu câu sau đây:
(a) Hàm phải kết thúc với câu lệnh return (b) Phải có câu lệnh return cho hàm
(c) Các câu lệnh return phép nằm vị trí thân hàm
(d) Khơng cần khai báo kiểu giá trị trả lại hàm hàm khơng có lệnh return
3 Chọn câu sai câu sau đây:
(a) Các biến khai báo thân hàm không phép trùng với tên đối (b) Các biến cục thân hàm chương trình dịch cấp phát nhớ
(c) Tất tham đối hình thức cấp phát nhớ tạm thời hàm gọi
(d) Kiểu tham đối thực phải giống (hoặc C++ tự động chuyển kiểu về) kiểu tham đối hình thức tương ứng với lời gọi hàm
4 Chọn câu sai câu sau đây: (a) Số lượng đối mặc định tùy ý
(b) Được phép khai báo tham đối mặc định vị trí danh sách đối (c) Vị trí đối mặc định phải liên tiếp cuối danh sách đối
(d) Với hàm khơng có biến mặc định số tham đối thực phải số tham số hình thức lời gọi hàm
5 Hàm có biến vào đại diện cho số byte số bit (1 byte bit), hàm trả lại tổng số bit
int total_bit (int bytes , int bits) {
int bits;
bits = 8* bytes + bits;
return bits; }
Hãy cho biết hàm viết hay sai Vì ?
(89)dành
cho
hội
đồng
nghiệm
thu
4.8 Lập trình với hàm đệ quy 81
7 Viết hàm tính diện tích hình vng diện tích hình trịn Áp dụng hàm tính phần diện tích giới hạn hình trịn bán kính r hình vng ngoại tiếp nó.
8 Viết hàm gồm đối x, y, z Hàm trả lại x ≤ y ≤ z ngược lại Áp dụng: Nhập số từ bàn phím, in dãy số theo thứ tự tăng dần
9 Viết hàm in n dấu sao, bắt đầu cột thứ k Áp dụng: in tam giác cân (gồm dấu sao) có độ dài cạnh đáy nhập từ bàn phím
10 Viết hàm tìm UCLN số Áp dụng: tìm BCNN (bội chung nhỏ nhất) số (BCNN(m, n) = m * n / UCLN(m, n))
11 Viết hàm kiểm tra số nguyên n có số nguyên tố Áp dụng: (a) In 100 số nguyên tố
(b) In số nguyên tố bé 1000
(c) In cặp số sinh đôi bé 1000 (Các số “sinh đôi” số nguyên tố mà khoảng cách chúng 2)
12 Số hoàn chỉnh n số tổng ước (trừ n) (Ví dụ = + + 3) Hãy in số hoàn chỉnh từ đến 1000
13 Nhập số tự nhiên chẵn n > Hãy kiểm tra số biểu diễn dạng tổng của số nguyên tố hay không ?
14 Viết hàm tính tổng số nguyên dương Nhập vào số nguyên dương a, b, c, d Sử dụng hàm để in kết a + b, a + b + c a + b + c + d.
15 Viết hàm input cho phép nhập giá trị cho biến nguyên hàm sum() trả lại tổng số nguyên Viết chương trình sử dụng hàm để nhập vào số nguyên in tổng chúng
16 Viết chương trình liệt kê nghiệm họ phương trình: ax2+ bx + = 0, hệ số a lấy tập số {1, −1, −2} b tương tự với {−2, 2, 1}.
Hướng dẫn: Viết hàm giải phương trình bậc với giá trị trả lại số nghiệm phương trình Hàm có đối a, b, c x1, x2, x1, x2 đối tham chiếu dùng để lưu 2 nghiệm phương trình Khai báo hai mảng A[3] B[3] để lưu hệ số cho đầu
17 Nhập số nguyên dương N Viết hàm đệ qui tính: (a) S1 = 1+2+ +NN
(b) S2 = √
12+ 22+ + N2
18 Viết hàm đệ qui tính n! Áp dụng chương trình tính tổ hợp chập k theo cơng thức: C(n, k) = n!/(k!(n− k)!.
(90)dành
cho
hội
đồng
nghiệm
thu
82 Hàm
(91)dành
cho
hội
đồng
nghiệm
thu
Chương 5 Mảng
5.1 Lập trình thao tác với mảng chiều
5.1.1 Ý nghĩa mảng
Khi cần lưu trữ dãy n phần tử liệu, cần khai báo n biến tương ứng với n tên gọi khác Điều khó khăn cho người lập trình để nhớ quản lý hết tất biến, đặc biệt n lớn Trong thực tế, hiển nhiên gặp nhiều liệu có liên quan đến mặt đó, ví dụ chúng có kiểu thể đối tượng: toạ độ vectơ, số hạng ma trận, sinh viên lớp dịng kí tự văn … Lợi dụng đặc điểm này, toàn liệu (cùng kiểu mơ tả đối tượng) cần chung tên gọi để phân biệt với đối tượng khác, để phân biệt liệu đối tượng ta sử dụng cách đánh số thứ tự cho chúng, từ việc quản lý biến dễ dàng hơn, chương trình gọn có tính hệ thống
Giả sử ta có vectơ không gian ba chiều, vec tơ cần biến để lưu toạ độ, để lưu toạ độ vectơ phải dùng đến biến, ví dụ x1, y1, z1 cho vectơ thứ x2, y2, z2 cho vectơ thứ hai Một kiểu liệu gọi mảng chiều cho phép ta cần khai báo biến v1 v2 để vectơ, v1 v2 chứa liệu đánh số thứ tự từ đến 2, ta ngầm định thành phần biểu diễn toạ độ x, thành phần biểu diễn toạ độ y thành phần có số thứ tự biểu diễn toạ độ z
Tóm lại, mảng dãy thành phần có kiểu kề liên tục nhớ Tất thành phần có tên tên mảng Để phân biệt thành phần với nhau, thành phần đánh số thứ tự (còn gọi số) từ hết mảng Như vậy, mảng có n thành phần thành phần cuối mảng đánh số n-1 Khi cần nói đến thành phần cụ thể mảng ta dùng tên mảng kèm theo số thành phần nằm cặp dấu []
(92)dành
cho
hội
đồng
nghiệm
thu
84 Mảng
Bảng 5.1: Phân bổ nhớ mảng chiều
Địa 200 201 202 203 204 205 206 207 208 209 210 211 … score score[0] score[1] score[2] score[3] score[4]
Chỉ số
5.1.2 Thao tác với mảng chiều
Khai báo
Để khai báo mảng chiều ta dùng cú pháp sau:
Type_Name array_name [SIZE ];
Trong đó, SIZE số biểu thị kích thước tức số thành phần (hoặc số phần tử) mảng Type_Name kiểu thành phần
Ví dụ, điểm x với tọa độ nguyên không gian chiều khai báo mảng gồm thành phần:
int x[3];
Trong đó, thành phần x x[0], x[1], x[2]
Tương tự, điểm số sinh viên khai báo mảng thành phần: int score [5];
Với kích thước mảng ta thường dùng tên hằng, ví dụ: const int NUMBER_OF_STUDENTS = 5;
int score [ NUMBER_OF_STUDENTS ];
Khai báo tương đương với int score[5], nhiên, sử dụng tên có nhiều thuận lợi sau, ví dụ số sinh viên thay đổi ta cần điều chỉnh dịng định nghĩa NUMBER_OF_STUDENTS thay phải thay đổi nhiều nơi chương trình có liên quan đến số sinh viên
Chú ý kích thước mảng phải biết trước chạy chương trình, khơng thể biến biểu thức liên quan đến biến Ví dụ:
int size;
cout << " Enter size of array : "; cin >> size;
int score [size ]; // sai
Sử dụng mảng
• Truy cập thành phần mảng
Để truy cập đến thành phần mảng, ta sử dụng tên mảng số đặt cặp dấu ngoặc [] Ví dụ, score[0] để điểm sinh viên thứ 0, score[i] điểm sinh viên thứ i (hiển nhiên i biến có giá trị cụ thể) Tổng quát, số biểu thức nguyên có giá trị nằm khoảng từ đến SIZE-1 Ví dụ:
i = 2;
(93)dành
cho
hội
đồng
nghiệm
thu
5.1 Lập trình thao tác với mảng chiều 85
Mặc dù mảng biểu diễn đối tượng áp dụng thao tác lên toàn mảng mà phải thực thao tác thơng qua thành phần mảng Ví dụ, nhập in liệu cho mảng score[5] câu lệnh:
cin >> score ; // sai
cout << score ; // sai
mà phải nhập, in cho phần tử từ score[0] đến score[4] Dĩ nhiên trường hợp này, phải cần đến lệnh lặp for:
int index ;
for ( index = ; index < NUMBER_OF_STUDENTS ; index ++) cin >> score [ index ] ;
Tương tự, giả sử phân số (gồm tử mẫu) khai báo mảng thành phần, cần cộng phân số a, b đặt kết vào c Không thể viết:
c = a + b ; // sai
mà cần phải tính phần tử c:
c[0] = a[0] * b[1] + a[1] * b[0] ; // tử số c[1] = a[1] * b[1] ; // mẫu số
• Truy cập ngồi miền số
Một điểm cần lưu ý truy nhập mảng tức thành phần nằm vùng số Trường hợp này, chương trình chạy cho kết sai Ví dụ với mảng int score[5], giả sử byte mảng đặt địa 200 (khi giá trị phần tử cuối score[4] đặt byte 208), ta dùng câu lệnh:
int i = 3; i = i + 2;
cout << score[i];
Chương trình tính i = tìm đến địa bytes tiếp sau score[4] 210 211 để lấy giá trị in hình Đây giá trị ngẫu nhiên có sẵn (thường gọi rác) khơng phải score Do vậy, chương trình chạy cho kết sai Đây lỗi không ý từ đầu khó phát trình gỡ lỗi
Một số ví dụ
1 # include <iostream >
3 using namespace std;
5 int main () {
7 int a[2] , b[2] , sum [2] , pro [2] ;
8 cout << " Enter numerator of fraction a: " ; cin >> a[0] ; cout << " Enter denomirator of fraction a: " ; cin >> a[1] ; 10 cout << " Enter numerator of fraction b: " ; cin >> b[0] ; 11 cout << " Enter denomirator of fraction b: " ; cin >> b[1] ; 12
(94)dành
cho
hội
đồng
nghiệm
thu
86 Mảng
14 sum [1] = a[1] * b[1] ; 15 pro [0] = a[0]*b[0]; 16 pro [1] = a[1] * b[1] ;
17 cout << "Sum of two fractions = " << sum [0] << '/' << sum [1] << endl; 18 cout << " Product of two fractions = " << pro [0] << '/' << pro [1] << endl; 19
20 return 0; 21 }
Hình 5.2: Tìm tổng, tích phân số # include <iostream >
2
3 using namespace std;
5 int main () {
7 const int MAX = 100; double SEQ[MAX ];
9 int count , numElement ;
10 int numPositive , numNegative , numZero ;
11 cout << " Enter the number of elements of sequence : " ; cin >> numElement ; 12 cout << " Enter elements of sequence : " ;
13 for ( count = 0; count < numElement ; count ++) 14 {
15 cout << "SEQ[" << count << "] = " ; 16 cin >> SEQ[count ];
17 } 18
19 numPositive = numNegative = numZero = 0; 20 for ( count = 0; count < numElement ; count ++) 21 {
22 if (SEQ[count ] > ) numPositive ++; 23 if (SEQ[count ] < ) numNegative ++; 24 if (SEQ[count ] == ) numZero ++; 25 }
26
27 cout << "The number of positives = " << numPositive << endl; 28 cout << "The number of negatives = " << numNegative << endl; 29 cout << "The number of zeros = " << numZero << endl;
30
31 return 0; 32 }
Hình 5.3: Nhập dãy số ngun, tính số số hạng dương, âm, khơng dãy # include <iostream >
2
3 using namespace std;
5 int main () {
(95)dành
cho
hội
đồng
nghiệm
thu
5.1 Lập trình thao tác với mảng chiều 87
9 int count , numElement ; 10 int minValue , minId ;
11 cout << " Enter the number of elements of sequence : " ; cin >> numElement ; 12 cout << " Enter elements of sequence : " << endl;
13 for (count = 0; count < numElement ; count ++) 14 {
15 cout << "SEQ[" << count << "] = " ; 16 cin >> SEQ[ count ];
17 } 18
19 minValue = SEQ [0]; 20 minId = 0;
21 for (count = 1; count < numElement ; count ++) 22 {
23 if (SEQ[ count ] < minValue )
24 {
25 minValue = SEQ[ count ]; 26 minId = count ;
27 }
28 } 29
30 cout << "The minimum value of sequences is " << minValue << " at position " <<
minId << endl;
31
32 return 0; 33 }
Hình 5.4: Tìm số bé dãy số In số vị trí dãy Chương trình sử dụng mảng SEQ để lưu dãy số, used_size số phần tử thực dãy, minValue lưu số bé tìm minId vị trí số dãy minValue khởi tạo giá trị (SEQ[0]), sau so sánh với số hạng lại, gặp số hạng nhỏ hơn, minValue nhận giá trị số hạng Quá trình so sánh tiếp tục hết dãy Vì số số hạng dãy biết trước (used_size), nên số lần lặp biết trước (used_size - lần lặp), sử dụng câu lệnh for cho ví dụ
Khởi tạo mảng
Cũng giống loại biến khác, q trình khai báo ta kết hợp khởi tạo giá trị cho mảng cú pháp sau:
Type_Name array_name [SIZE] = {value1 , value2 , , valuen }; Type_Name array_name [] = {value1 , value2 , , valuen };
• Dạng khai báo thứ cho phép khởi tạo mảng dãy giá trị cặp dấu {}, giá trị cách dấu phảy (,), giá trị gán cho phần tử mảng phần tử thứ hết dãy Số giá trị bé số phần tử (n <= SIZE) Các phần tử mảng chưa có giá trị khơng xác định chương trình gán giá trị
(96)dành
cho
hội
đồng
nghiệm
thu
88 Mảng
Ví dụ:
• Khai báo phân số a, b, c; a = 1/3 b = 3/5: int a[2] = {1, 3} , b[2] = {3, 5} , c[2] ;
Ở đây, ta ngầm qui ước thành phần (chỉ số 0) tử thành phần thứ hai (chỉ số 1) mẫu phân số
• Khai báo dãy data chứa số thực độ xác gấp đơi:
double data [] = { 0, 0, 0, 0, }; // khởi tạo tạm thời 0
Trong trường hợp này, mảng data có số thành phần cố định
5.1.3 Mảng hàm
Đối hàm mảng
Hiển nhiên, phần tử đơn lẻ mảng xem giá trị thơng thường nên phép xuất lời gọi hàm (truyền cho hàm), ví dụ: giả sử ta có hàm so sánh số double getMax(double x, double y) mảng thực double A[n]; ta so sánh số số thứ mảng A lời gọi getMax(A[0], A[5]); chẳng hạn:
cout << getMax (A[0], A[5]);
Tổng quát hơn, thường hay xây dựng hàm làm việc toàn mảng vectơ hay ma trận phần tử Khi đó, tham đối hàm phải mảng liệu này, ví dụ cần xây dựng hàm tìm phần tử lớn mảng 10 phần tử, ta khai báo hàm sau:
int getMax (int A [10]);
Như vậy, quan với 10 người có bảng lương int salary_table[10], ta gọi đến hàm để tìm lương cao lệnh gọi:
cout << getMax ( salary_table );
Trong ví dụ trên, có mảng với số phần tử 10 gọi đến hàm getMax Cách xây dựng hàm với số phần tử cố định (10) thiếu tính linh hoạt, C++ cho phép xây dựng hàm không cần khai báo trước số phần tử, nhiên để dễ làm việc, số phần tử tách tham đối thứ 2, ta có khai báo hồn chỉnh:
int getMax (int A[], int size );
Bây để tìm lương cao ta gọi:
cout << getMax ( slary_table , 10);
và phép tìm thành phần lớn vectơ int vector[12] lệnh gọi:
cout << getMax (vector , 12);
(97)dành
cho
hội
đồng
nghiệm
thu
5.1 Lập trình thao tác với mảng chiều 89
1 # include <iostream >
3 using namespace std;
5 void Setup (int A[], int used_size ) {
7 cout << " Enter " << used_size << " numbers :\n"; for (int index = 0; index < used_size ; index ++) cin >> A[ index ];
10 return; 11 }
12
13 int GetMax (int A[], int size) 14 {
15 int res = A[0];
16 for (int index = 0; index < size; index ++) 17 if (A[ index ] > res) res = A[ index ]; 18 return res;
19 } 20
21 int main () 22 {
23 int salary_table [5] ; 24 Setup ( salary_table , 5);
25 cout << " Highest of salary : " ; cout << GetMax ( salary_table , 5) << endl;
26 cout << " Highest salary of first three persons : " ; cout << GetMax ( salary_table ,
3) << endl;
27
28 return 0; 29 }
Hình 5.5: Quản lý bảng lương
Tương tự lời gọi getMax(salary_table, 5) getMax(salary_table, 3), ta gọi hàm setup để nhập liệu cho mảng với kích thước Ví dụ setup(score, 3) (nhập điểm cho sinh viên đầu tiên)
Như ý chương trước, tham đối hàm khai báo khơng cần thiết phải ghi tên đối, ví dụ khai báo:
int getMax (int[], int);
Dưới ví dụ tính tích vơ hướng vectơ có số thành phần (chỉ cần tham đối để số thành phần chung cho vec tơ)
1 # include <iostream >
3 using namespace std;
5 void Setup (int[], int);
6 int Procduct (const int[], const int[], int);
8 int main () {
(98)dành
cho
hội
đồng
nghiệm
thu
90 Mảng
11 int index , numElement ;
12 cout << " Enter number of elements : " ; cin >> numElement ; 13 cout << "Fill up vector A " ;
14 Setup (vector_A , numElement ); 15 cout << "Fill up vector B " ; 16 Setup (vector_B , numElement ); 17
18 cout << " Scalar product of A and B is " << Procduct (vector_A , vector_B ,
numElement ) << endl;
19
20 return 0; 21 }
22
23 void Setup (int A[], int size) 24 {
25 cout << " Enter " << size << " numbers :\n"; 26 for (int index = 0; index < size; index ++) 27 cin >> A[ index ];
28 return; 29 }
30
31 int Procduct (const int A[], const int B[], int size) 32 {
33 int res = 0;
34 for (int index = 0; index < size; index ++) 35 res += A[ index ] * B[ index ];
36 return res; 37 }
Hình 5.6: Tích vơ hướng
Trường hợp hàm có nhiều tham đối mảng với kích thước sử dụng khác ta cần thêm cho hàm mảng tham đối kích thước
Ngăn ngừa việc thay đổi giá trị mảng – từ khóa const
Trong chương 4, trình bày hàm ta đề cập đến việc thay đổi giá trị biến đối hàm biến tham chiếu Bản chất việc tác động lên biến tham chiếu tác động đến địa ô nhớ mà biến tham chiếu hướng tới Một biến mảng gần giống biến tham chiếu tên mảng địa nhớ nơi mảng bắt đầu Vì vậy, thao tác đối mảng thực chất thực mảng ngồi truyền cho hàm lời gọi Nói cách khác, thay đổi đối mảng thực làm thay đổi mảng tương ứng Do vậy, để hàm khơng vơ tình thay đổi đầu vào (tham đối mảng), tham đối cần khai báo kèm với từ khóa const
Ví dụ sau mô tả việc in giá trị dãy số lưu mảng sample_array tăng thêm đơn vị so với lưu trữ gốc Chương trình “vơ tình” thay đổi giá trị mảng sample_array dù ta muốn giữ nguyên giá trị gốc để dùng sau
1 # include <iostream >
3 using namespace std;
(99)dành
cho
hội
đồng
nghiệm
thu
5.1 Lập trình thao tác với mảng chiều 91
7 int main () {
9 int sample_array [5] = { 1, 2, 3, 4, };
10 cout << " Sequence of elements are growed one unit : \n" ; 11 Display ( sample_array , 5);
12 cout << " Origin Sequence : \n" ;
13 for (int index = 0; index < 5; index ++)
14 cout << sample_array [index ] << " "; // In lai mang sample_array 15 cout << endl;
16
17 return 0; 18 }
19
20 void Display (int A[], int size) 21 {
22 for (int index = 0; index < 5; index ++)
23 cout << ++(A[ index ]) << " "; // Tang them cho phan tu mang va in 24 cout << endl;
25 return; 26 }
Hình 5.7: Mảng bị sửa hàm
Trong ví dụ này, sample_array khởi tạo số nguyên 1, 2, 3, 4, sau in xong giá trị thay đổi thành 2, 3, 4, 5, Lỗi chương trình nằm câu lệnh ++A[i], làm tăng giá trị phần tử thứ i mảng (sample_array[i]) trước in
Để giữ nguyên giá trị cũ mảng sample_array, ta cần thêm từ khóa const vào trước tham đối int A[], chương trình khơng chạy (báo lỗi) hàm display ta sử dụng câu lệnh cout << ++A[index]; để khắc phục ta cần sửa lại câu lệnh thành cout << A[index] + 1; ví dụ
1 # include <iostream >
3 using namespace std;
5 void Display (const int[], int);
7 int main () {
9 int sample_array [5] = { 1, 2, 3, 4, };
10 cout << " Sequence of elements are growed one unit : \n" ; 11 Display ( sample_array , 5);
12 cout << " Origin Sequence : \n" ;
13 for (int index = 0; index < 5; index ++)
14 cout << sample_array [index ] << " "; // In lai mang sample_array 15 cout << endl;
16
17 return 0; 18 }
19
20 void Display (const int A[], int size) 21 {
(100)dành
cho
hội
đồng
nghiệm
thu
92 Mảng
23 cout << A[ index ] + << " "; // Tang them cho phan tu mang va in 24 cout << endl;
25 return; 26 }
Hình 5.8: Tham đối const
Kỹ thuật khai báo tham đối (const) sử dụng cho nhiều kiểu tham đối khác để ngăn ngừa việc vơ tình làm thay đổi giá trị gốc biến truyền cho tham đối
Tính thiếu quán dùng khai báo tham đối (const)
Giả sử ta có hàm FUNC với khai báo tham đối mảng A[] (không cho phép thay đổi A), hàm gọi đến hàm func để xử lý mảng A[], func lại không khai báo tham đối mảng (cho phép thay đổi A), ví dụ:
void func(int A[], int size) {
}
void FUNC(const int A[], int size) {
func(A, 10); }
Khi hầu hết chương trình dịch C++ báo lỗi cảnh báo: “Invalid conversion const int* to int*” Khi đó, nói chung tham đối mảng hàm func nên khai báo
Giá trị trả lại hàm mảng
Một hàm trả lại giá trị mảng giá trị thông thường khác (int, double, …), ngồi việc kết trả lại hàm (thơng qua câu lệnh return) trỏ trỏ đến dãy kết (ngầm hiểu mảng) Kỹ thuật sử dụng trỏ trình bày chương sau giáo trình
Hiện tại, để lấy kết mảng, ta khai báo thêm đối mảng hàm để lưu giữ kết thay cho giá trị trả lại hàm Ví dụ cộng hai vec tơ minh họa điều # include <iostream >
2
3 using namespace std;
5 void SumVector (int A[], int B[], int C[], int n) {
7 for (int index = 0; index < n; index ++) C[ index ] = A[ index ] + B[ index ]; return;
10 } 11
12 int main () 13 {
14 int a[3] = {1, 2, 3}; 15 int b[3] = {0, 2, 4}; 16 int c[3];
17 SumVector (a, b, c, 3);
(101)dành
cho
hội
đồng
nghiệm
thu
5.1 Lập trình thao tác với mảng chiều 93
19 for (int index = 0; index < 2; index ++)
20 cout << c[index ] << ", "; // result is vector (1, 4, 7) 21 cout << c[2] << " )" << endl;
22 return 0; 23 }
Hình 5.9: Trả kết tham đối mảng
Như vậy, hai đối mảng A, B gần bắt buộc phải có (là input), hàm cịn có đối mảng C khác để lưu giữ vectơ tổng (output) Các mảng A, B khai báo (kèm từ khóa const), mảng C cần lưu kết (thay đổi giá trị) nên khơng kèm từ khóa Kiểu trả lại hàm void (vì thực chất hàm khơng trả lại giá trị, ghi kết vào mảng C)
5.1.4 Tìm kiếm xếp
Tìm kiếm
Cho dãy phần tử (được lưu dạng mảng data_arr[]) phần tử target cho trước Bài toán đặt là: Hãy trả lời target có phần tử dãy hay khơng ? Nếu có vị trí thứ mảng ?
Hàm int search(int A[], int x) trình bày cho phép tìm kiếm x mảng A cách so sánh phần tử A[i] mảng với x, có phần tử A[i] trùng với x, thuật toán dừng trả lại vị trí i phần tử này, khơng thuật tốn trả lại -1 Hàm sử dụng biến found_at để lưu kết khởi tạo trước -1 (ngầm định khơng tìm thấy), q trình so sánh tìm thấy, found_at đặt lại vị trí phần tử mảng, ngược lại duyệt hết mảng mà khơng tìm thấy target found_at = -1
1 # include <iostream >
3 using namespace std;
5 const int MAX = 50;
6 void Setup (int A[], int used_size );
7 int Search (const int A[], int used_size , int target );
9 int main () 10 {
11 int data_arr [MAX], used_size ; 12 int target , pos;
13
14 cout << " Enter number of elements : "; 15 cin >> used_size ;
16 Setup (data_arr , used_size );
17 cout << " Enter a number to search for: "; 18 cin >> target ;
19
20 pos = Search (data_arr , used_size , target ); 21
22 if (pos)
23 cout << target << " is stored in sequence at position " << pos << endl 24 << "( Remember : The first position is 0.)\n";
(102)dành
cho
hội
đồng
nghiệm
thu
94 Mảng
26 cout << target << " is not on the sequence \n"; 27
28 return 0; 29 }
30
31 void Setup (int A[], int used_size ) 32 {
33 cout << " Enter " << used_size << " numbers : "; 34 for (int index = 0; index < used_size ; index ++) 35 cin >> A[ index ];
36 return; 37 }
38
39 int Search (const int A[], int used_size , int target ) 40 {
41 int found_at = -1; // ngam dinh khong tim thay target 42 for (int index = 0; index < used_size ; index ++)
43 if (A[ index ] == target )
44 {
45 found_at = index ;
46 break;
47 }
48 return found_at ; 49 }
Hình 5.10: Tìm kiếm mảng Sắp xếp
Giả sử cần xếp tăng dần giá trị dãy số int A[n] Thuật toán chọn (Selection Sort) thuật toán đơn giản, dễ hiểu để thực công việc Thuật toán tiến hành cách chọn dần số hạng bé nhất, bé “thứ hai”, “thứ ba”, … để lắp vào vị trí (thứ nhất, thứ hai, thứ ba … ) dãy
Để thuận lợi cho trình bày chi tiết ta tạm quay lại cách đánh số phần tử đến n thực dãy cụ thể gồm số hạng: 30, 25, 8, 12, 4, 21
Bảng 5.11: Dãy ban đầu 30 25 12 21
• Đầu tiên, thuật tốn tìm chọn số bé dãy (từ vị trí đến n), giả sử A[k] đặt số lên đầu dãy cách tráo đổi với A[1] Như vậy, sau bước ta có số bé nằm vị trí (đã xếp) với n–1 số lại chưa xếp
Trong ví dụ trên, số bé tìm (A[5]), tráo đổi với A[1] (30) ta dãy Bảng 5.12: Lần tráo đổi
4 25 12 30 21
(103)dành
cho
hội
đồng
nghiệm
thu
5.1 Lập trình thao tác với mảng chiều 95
Trong ví dụ trên, số bé “thứ hai” tìm A[3] = 8, tráo đổi với A[2] ta dãy với vị trí xếp
Bảng 5.13: Lần tráo đổi thứ thứ hai 25 12 30 21
• Q trình tiếp tục phần tử thứ n-1 Hiển nhiên phần tử thứ n không cần phải xếp
Áp dụng với ví dụ trên, bước cịn lại sau:
Bảng 5.14: Q trình xếp
4 25 12 30 21 Chọn 12 đổi chỗ với 25 12 25 30 21 Chọn 21 đổi chỗ với 25 12 21 30 25 Chọn 25 đổi chỗ với 30 12 21 25 30 Sắp xếp xong
Tổng quát: Giả sử i-1 vị trí, ta tìm số bé dãy cịn lại (từ vị trí thứ i đến n) trao đổi số với số vị trí thứ i Q trình tiếp tục đến lắp đủ n-1 phần tử vào vị trí (phần tử thứ n hiển nhiên tự vào vị trí) Như thuật tốn cần n-1 bước tìm chọn đổi chỗ
Có thể mơ tả thuật toán lược đồ:
for (i = to n - 1) // chỉ cần n-1 vị trí đầu tiên {
- Tìm số bé đoạn từ i + đến n - Đổi chỗ số vừa tìm với số thứ i }
Để tìm số bé đoạn từ i + đến n ta dùng vòng lặp for tương tự hàm getMax (xem phần 5.1.3 – hàm tìm phần tử lớn nhất) Dưới chương trình hồn chỉnh cho hàm xếp mảng
1 # include <iostream >
3 using namespace std;
5 const int MAX = 50;
6 void Setup (int A[], int n); // ham nhap day so void Print (const int A[], int n); // ham in day so int Sort(int A[], int n); // ham sap xep
10 int main () 11 {
12 int data_arr [MAX ]; 13 int used_size , i, j; 14 int tmp;
(104)dành
cho
hội
đồng
nghiệm
thu
96 Mảng
16 cout << " Enter the number of elements of sequence : " ; 17 cin >> used_size ;
18 Setup (data_arr , used_size ); 19 Sort(data_arr , used_size );
20 cout << "In sorted order the numbers are :\n"; 21 Print (data_arr , used_size );
22
23 return 0; 24 }
25
26 void Setup (int A[], int n) 27 {
28 cout << " Enter " << n << " numbers : "; 29 for (int index = 0; index < n; index ++) 30 cin >> A[ index ];
31 return; 32 }
33
34 void Print (const int A[], int n) // ham in day so 35 {
36 for (int index = 0; index < n; index ++) 37 cout << A[ index ] << " ";
38 cout << endl; 39 return;
40 } 41
42 int Sort(int A[], int n) // ham sap xep 43 {
44 int tmp , min_value ; 45 int id_min ;
46 for (int id1 = 0; id1 < n -1; id1 ++)
47 { // chon so be nhat 48 min_value = A[id1 ];
49 id_min = id1;
50 for (int id2 = id1 +1; id2 < n; id2 ++) 51 if (A[id2] < min_value )
52 {
53 min_value = A[id2 ];
54 id_min = id2;
55 }
56 tmp = A[id1 ]; // doi A[id1] voi A[id2] 57 A[id1] = A[ id_min ];
58 A[ id_min ] = tmp; 59 }
60 }
Hình 5.15: Sắp xếp chọn
Để che giấu bớt chi tiết, ta nên tách đoạn chương trình chọn số bé trao đổi số sort thành hàm riêng swap(int &v1, int &v2) (xem chương 4) get_index_of_smallest(const int A[], int start_index, int last_index) phiên chương trình xếp
(105)dành
cho
hội
đồng
nghiệm
thu
5.1 Lập trình thao tác với mảng chiều 97
2
3 using namespace std;
5 const int MAX = 50;
6 void Setup (int A[], int n); // ham nhap day so void Print (const int A[], int n); // ham in day so void Swap(int &v1 , int &v2); // ham doi gia tri // ham tim chi so cua so be nhat day
10 int Get_Index_Of_Smallest (const int A[], int start_index , int last_index ); 11 int Sort(int A[], int n); // ham sap xep
12
13 int main () 14 {
15 int data_arr [MAX ]; 16 int used_size , i, j; 17 int tmp;
18 cout << "This program sorts numbers from lowest to highest \n"; 19 cout << " Enter the number of elements of sequence : " ;
20 cin >> used_size ;
21 Setup (data_arr , used_size ); 22 Sort(data_arr , used_size );
23 cout << "In sorted order the numbers are :\n"; 24 Print (data_arr , used_size );
25
26 return 0; 27 }
28
29 void Setup (int A[], int n) 30 {
31 cout << " Enter " << n << " numbers : "; 32 for (int index = 0; index < n; index ++) 33 cin >> A[ index ];
34 return; 35 }
36
37 void Print (const int A[], int n) // ham in day so 38 {
39 for (int index = 0; index < n; index ++) 40 cout << A[index ] << " ";
41 cout << endl; 42 return;
43 } 44
45 void Swap(int &v1 , int &v2) 46 {
47 int tmp; 48 tmp = v1; 49 v1 = v2; 50 v2 = tmp; 51 }
52
53 int Get_Index_Of_Smallest (const int A[], int start_index , int last_index ) 54 {
(106)dành
cho
hội
đồng
nghiệm
thu
98 Mảng
56 int id_min = start_index ;
57 for (int i = start_index +1; i <= last_index ; i++) 58 if (A[i] < min_value )
59 {
60 min_value = A[i]; 61 id_min = i;
62 }
63 return id_min ; 64 }
65
66 int Sort(int A[], int n) // ham sap xep 67 {
68 int id_min ;
69 for (int i = 0; i < n -1; i++) 70 {
71 id_min = Get_Index_Of_Smallest (A, i, n -1); // chon so be nhat 72 Swap(A[i], A[ id_min ]);
73 } 74 }
Hình 5.16: Sắp xếp chọn (bản cải tiến)
5.2 Lập trình thao tác với mảng nhiều chiều
5.2.1 Mảng chiều
Để thuận tiện việc biểu diễn loại liệu phức tạp ma trận bảng biểu có nhiều tiêu chí, C++ đưa kiểu liệu mảng nhiều chiều Tuy nhiên, việc sử dụng mảng với số chiều lớn khó lập trình sử dụng, mục bàn đến mảng hai chiều
Đối với mảng chiều m phần tử, thành phần lại mảng chiều n phần tử (ví dụ hình máy tính mảng gồm 24 phần tử (dòng), phần tử lại mảng chứa 80 kí tự) ta gọi mảng hai chiều với số phần tử (hay kích thước) chiều m n Ma trận minh hoạ cho hình ảnh mảng hai chiều, gồm m dịng n cột, tức chứa m x n phần tử, hiển nhiên phần tử có kiểu Tuy nhiên, mặt chất mảng hai chiều tập hợp với m x n phần tử kiểu mà tập hợp với m thành phần, thành phần mảng chiều với n phần tử Điểm nhấn mạnh giải thích cụ thể chương sau
Bảng 5.17: Minh họa mảng hai chiều
0
0 ? ? ? ?
1 ? ? ? ?
2 ? ? ? ?
(107)dành
cho
hội
đồng
nghiệm
thu
5.2 Lập trình thao tác với mảng nhiều chiều 99
chiều, số (dòng, cột) tính từ Thực chất nhớ tất 12 phần tử mảng liên dòng mảng minh hoạ hình
Bảng 5.18: Phân bổ nhớ mảng hai chiều
? ? ? ? ? ? ? ? ? ? ? ?
dòng dịng dịng
Và vậy, trơng giống với (và chất là) mảng chiều Tuy nhiên, thời điểm này, để đơn giản, ta hình dung sử dụng mảng chiều hình ảnh ma trận
5.2.2 Thao tác với mảng hai chiều
Khai báo.
Để khai báo mảng hai chiều, ta dùng cú pháp sau:
Typename Array_name [ SIZE1 ][ SIZE2 ];
Trong SIZE1, SIZE2 số biểu thị kích thước (hai chiều) mảng, tức số thành phần (hoặc số phần tử) mảng Typename kiểu thành phần Ví dụ:
const int NUM_STUDENTS = 20;
const int NUM_COURSES = 3;
int mark[ NUM_STUDENTS ][ NUM_COURSES ];
Mảng mark dùng để ghi điểm số sinh viên, hiểu mark[i][j] điểm sinh viên thứ i đạt môn học thứ j
Trong khai báo, mảng chiều, mảng hai chiều khởi tạo dãy dòng giá trị, dòng cách dấu phẩy, dòng bao cặp ngoặc {} toàn giá trị khởi tạo nằm cặp dấu {} đơn giản khởi tạo dãy liên tục giá trị (như ví dụ bên dưới), chương trình tự động nhận biết gán giá trị cho dịng mảng Ví dụ:
int mark[ NUM_STUDENTS ][ NUM_COURSES ] = { 1, 2, 3, 4, 5, 6, };
Trong khởi tạo sinh viên danh sách có số điểm môn 1, 2, sinh viên thứ hai có điểm 4, 5, Điểm mơn sinh viên thứ ba 7, môn khác chưa xác định
Đối với trường hợp khai báo có khởi tạo, bỏ qua kích thước thứ (số dịng) kích thước thứ hai (số cột) bắt buộc phải có, số dịng xác định thơng qua khởi tạo Với ví dụ ta phép khai báo:
int mark [][ NUM_COURSES ] = { 1, 2, 3, 4, 5, 6, };
trong khai báo chương trình tự động xác định số dòng
Qua thảo luận người đọc tự rút kết luận kích thước thứ hai (số cột) mảng bắt buộc phải khai báo
Trường hợp khởi tạo thiếu số thành phần ta nên dùng thêm cặp {} cách tường minh để tránh gây nhầm lẫn Ví dụ:
(108)dành
cho
hội
đồng
nghiệm
thu
100 Mảng
Khởi tạo thiếu điểm môn cho sinh viên 1, thiếu môn cho sinh viên điểm sinh viên khởi tạo đầy đủ Các thành phần thiếu mặc định
Sử dụng mảng hai chiều
• Tương tự mảng chiều, chiều mảng đánh số từ
• Khơng sử dụng thao tác toàn mảng mà phải thực thơng qua phần tử mảng
• Để truy nhập phần tử mảng, ta sử dụng tên mảng kèm theo số vị trí dịng cột phần tử
Ví dụ Hình 5.19 minh họa cho việc in hình điểm số sinh viên khai báo khởi tạo Trong chương trình, để truy cập đến thành phần mảng ta cần dùng số i j cho dòng cột
1 # include <iostream >
3 using namespace std;
5 int main () {
7 const int NUM_STUDENTS = 20; const int NUM_COURSES = 3; int used_num_students ; 10
11 used_num_students = 3;
12 int mark[ NUM_STUDENTS ][ NUM_COURSES ] = { 1, 2, 3, 4, 5, 6, }; 13 for (int i = 0; i < used_num_students ; i++)
14 {
15 cout << " Marks of student #" << i+1 << " is: "; 16 for (int j = 0; j < NUM_COURSES ; j++)
17 cout << mark[i][j] << " "; 18 cout << endl;
19 }
20 return 0; 21 }
Hình 5.19: in hình điểm số sinh viên
Output hình 5.19
Marks of student #1 is 3 Marks of student #2 is 6 Marks of student #3 is 0
Với sinh viên thứ ba, điểm môn thứ hai thứ ba 0, điểm môn sinh viên chưa khởi tạo Lý khai báo biến đó, chương trình dịch bố trí biến chiếm số bytes nhớ tự động khởi tạo giá trị “rỗng” cho biến (0, NULL, … )
(109)dành
cho
hội
đồng
nghiệm
thu
5.2 Lập trình thao tác với mảng nhiều chiều 101
sinh viên thứ 21 (nằm vùng khai báo mảng mark) chương trình chấp nhận, nhiên giá trị in giá trị ngẫu nhiên nằm ô nhớ thường gọi “rác”
Tham đối hàm mảng hai chiều
Tương tự với mảng chiều, mảng hai chiều đối hàm xử lý mảng Ví dụ cần viết hàm nhập giá trị cho mảng, hàm in giá trị mảng hình, hàm in giá trị trung bình dịng, cột mảng, …
Trong mảng chiều tham gia làm đối hàm, đối không cần thiết phải khai báo kích thước, thay vào ta bổ sung tham đối để kích thước tối đa mảng kích thước sử dụng thực tế mảng tùy trường hợp sử dụng
Đối với mảng hai chiều, tham đối mảng không cần phải khai báo kích thước thứ (số dịng), nhiên bắt buộc phải khai báo kích thước thứ hai (số cột)
Các từ khóa const, … sử dụng giống mảng chiều Ví dụ minh họa cho hàm nhập mảng, tính điểm trung bình sinh viên môn học, in ấn kết thành dạng bảng biểu hình Trong chương trình ngồi mảng mark, ta cần khai báo thêm hai mảng stu_av[NUM_STUDENTS] course_av[NUM_COURSES] để lưu trữ điểm trung bình sinh viên điểm trung bình mơn học
1 # include <iostream > # include <iomanip >
4 using namespace std;
6 const int NUM_STUDENTS = 20; // so sinh vien toi da const int NUM_COURSES = 5; // so mon hoc toi da
8 double stu_av [ NUM_STUDENTS ]; // mang chua dtb cua tung sinh vien double course_av [ NUM_COURSES ]; // mang chua dtb theo tung mon hoc 10
11 void Fillup (int mark [][ NUM_COURSES ], int num_students , int num_courses ); 12 void ComputeStudentAve (const int mark [][ NUM_COURSES ], int num_students , int
num_courses , double stu_av []);
13 void ComputeCourseAve (const int mark [][ NUM_COURSES ], int num_students , int
num_courses , double course_av []);
14 void Display (const int mark [][ NUM_COURSES ], int num_students , int num_courses ); 15
16 int main () 17 {
18 int mark[ NUM_STUDENTS ][ NUM_COURSES ]; 19 int num_students = 4;
20 int num_courses = 3;
21 cout << " Enter real number of students : "; cin >> num_students ; 22 cout << " Enter real number of courses : "; cin >> num_courses ; 23 Fillup (mark , num_students , num_courses );
24 ComputeStudentAve (mark , num_students , num_courses , stu_av ); 25 ComputeCourseAve (mark , num_students , num_courses , course_av ); 26 Display (mark , num_students , num_courses );
27 return 0; 28 }
29
(110)dành
cho
hội
đồng
nghiệm
thu
102 Mảng
32 for (int st_id = 0; st_id < num_students ; st_id ++) 33 {
34 cout << " Enter " << num_courses << " marks of student #" << st_id + << ": "
;
35 for (int crs_id = 0; crs_id < num_courses ; crs_id ++) 36 cin >> mark[ st_id ][ crs_id ];
37 }
38 return; 39 }
40
41 void ComputeStudentAve (const int mark [][ NUM_COURSES ], int num_students , int
num_courses , double stu_av [])
42 {
43 for (int st_id = 0; st_id < num_students ; st_id ++) 44 {
45 double sum = 0;
46 for (int crs_id = 0; crs_id < num_courses ; crs_id ++) 47 sum = sum + mark[ st_id ][ crs_id ];
48 stu_av [ st_id ] = sum/ num_courses ; 49 }
50 return; 51 }
52
53 void ComputeCourseAve (const int mark [][ NUM_COURSES ], int num_students , int
num_courses , double course_av [])
54 {
55 for (int crs_id = 0; crs_id < num_courses ; crs_id ++) 56 {
57 double sum = 0;
58 for (int st_id = 0; st_id < num_students ; st_id ++) 59 sum = sum + mark[ st_id ][ crs_id ];
60 course_av [ crs_id ] = sum/ num_students ; 61 }
62 return; 63 }
64
65 void Display (const int mark [][ NUM_COURSES ], int num_students , int num_courses ) 66 {
67 cout.setf(ios :: fixed ); 68 cout.setf(ios :: showpoint ); 69 cout precision (1);
70 cout << "\ nCourse marks and average mark of students \n"; 71 cout << setw (10) << " Student ";
72 for (int crs_id = 1; crs_id <= num_courses ; crs_id ++) cout << setw (9) << "Crs#"
<< crs_id ;
73 cout << setw (10) << "Ave" << endl;
74 for (int st_id = 0; st_id < num_students ; st_id ++) 75 {
76 cout << setw (10) << st_id + 1;
77 for (int crs_id = 0; crs_id < num_courses ; crs_id ++) 78 cout << setw (10) << mark[ st_id ][ crs_id ];
79 cout << setw (10) << stu_av [ st_id ] << endl; 80 }
(111)dành
cho
hội
đồng
nghiệm
thu
5.2 Lập trình thao tác với mảng nhiều chiều 103
82 for (int crs_id = 0; crs_id < num_courses ; crs_id ++) 83 cout << setw (10) << course_av [ crs_id ];
84 cout << endl; 85 return;
86 }
Hình 5.20: Tính điểm trung bình Giá trị trả lại hàm mảng hai chiều
Như biết, thời điểm ta chưa trình bày trỏ nên chưa có cách để hàm trả lại giá trị mảng Tuy nhiên, khai báo mảng kết đối hàm hàm lưu lại kết vào đối
Ví dụ minh họa hàm nhân ma trận: Cho ma trận A (m x n) B (n x p) Tính ma trận C = A x B, C có kích thước m x p Các ma trận đầu vào (A, B) hiển nhiên đối (const) hàm Ma trận kết C khai báo đối thứ (không const) hàm kết lưu
Để nhân ma trận, số cột A phải số dòng B, phần tử dịng i, cột j ma trận kết (C[i][j]) tính tích vơ hướng dòng i A cột j B
1 # include <iostream > # include <iomanip >
4 using namespace std;
6 int main () {
8 double A[10][10] , B[10][10] , C [10][10] ;
9 int m, n, p ; // cac kich thuoc cua ma tran 10 int i, j, k ; // cac chi so vong lap
11
12 cout << " Enter sizes of two matrixes : " ; cin >> m >> n >> p; 13
14 cout << " Enter values of matrix A(" << m << " x " << n << "):\n" ; 15 // Nhap ma tran A
16 for (i = 0; i < m; i++) 17 for (j = 0; j < n; j++) 18 cin >> A[i][j] ; 19
20 cout << " Enter values of matrix B(" << n << " x " << p << "):\n" ; 21 // Nhap ma tran B
22 for (i = 0; i < n; i++) 23 for (j = 0; j < p; j++) 24 cin >> B[i][j] ; 25
26 // Tinh ma tran C = A x B 27 for (i = 0; i < m; i++) 28 for (j = 0; j < p; j++) 29 {
30 C[i][j] = 0;
31 for (k = 0; k < n; k++) C[i][j] += A[i][k]*B[k][j] ; 32 }
(112)dành
cho
hội
đồng
nghiệm
thu
104 Mảng
34 // Hien thi ket qua
35 cout << " Result Matrix C(" << m << " x " << p << "):\n" ; 36 cout << setiosflags (ios :: showpoint ) << setprecision (2) ; 37 for (i = 0; i < m; i++)
38 for (j = 0; j < p; j++) 39 {
40 if (j == 0) cout << endl; 41 cout << setw (6) << C[i][j] ; 42 }
43 cout << endl; 44
45 return 0; 46 }
Hình 5.21: Nhân ma trận
Để chương trình gọn hơn, thao tác nhập, nhân ma trận, in kết viết thành hàm Hình5.22
1 # include <iostream > # include <iomanip >
4 using namespace std;
6 const int SIZE = 10; // so dong , cot toi da cua cac ma tran A, B, C
8 void Fillup (double [][ SIZE], int, int);
9 void ProcductMatrix (const double[][ SIZE], const double[][ SIZE], double[][ SIZE], int, int, int);
10 void Display (const double[][ SIZE], int, int); 11
12 int main () 13 {
14 double A[SIZE ][ SIZE], B[SIZE ][ SIZE], C[SIZE ][ SIZE] ;
15 int used_size1 , used_size2 , used_size3 ; // cac kich thuoc cua ma tran 16
17 cout << " Enter sizes of two matrixes : " ; cin >> used_size1 >> used_size2 >>
used_size3 ;
18 cout << " Enter values of matrix A(" << used_size1 << " x " << used_size2 << "):\n
" ;
19 Fillup (A, used_size1 , used_size2 );
20 cout << " Enter values of matrix B(" << used_size2 << " x " << used_size3 << "):\n
" ;
21 Fillup (B, used_size2 , used_size3 );
22 ProcductMatrix (A, B, C, used_size1 , used_size2 , used_size3 );
23 cout << " Result Matrix C(" << used_size1 << " x " << used_size3 << "):\n" ; 24 Display (C, used_size1 , used_size3 );
25 cout << endl; 26
27 return 0; 28 }
29
30 void Fillup (double matrix [][ SIZE], int num_row , int num_col ) 31 {
(113)dành
cho
hội
đồng
nghiệm
thu
5.3 Lập trình thao tác với xâu kí tự 105
33 for (int j = 0; j < num_col ; j++) 34 cin >> matrix [i][j] ;
35 } 36
37 void ProcductMatrix (const double A[][ SIZE], const double B[][ SIZE], double res [][ SIZE
], int size1 , int size2 , int size3 )
38 {
39 int i, j, k;
40 for (i = 0; i < size1 ; i++) 41 for (j = 0; j < size2 ; j++) 42 {
43 res[i][j] = 0;
44 for (k = 0; k < size3 ; k++) res[i][j] += A[i][k]*B[k][j] ; 45 }
46 } 47
48 void Display (const double matrix [][ SIZE], int num_row , int num_col ) 49 {
50 cout << setiosflags (ios :: showpoint ) << setprecision (2) ; 51 for (int i = 0; i < num_row ; i++)
52 {
53 for (int j = 0; j < num_col ; j++) 54 cout << setw (6) << matrix [i][j] ; 55 cout << endl;
56 } 57 }
Hình 5.22: Nhân ma trận
5.3 Lập trình thao tác với xâu kí tự
Một xâu kí tự dãy kí tự (kể dấu cách), lưu mảng kí tự Tuy nhiên, để máy nhận biết mảng kí tự xâu, cần thiết phải có kí tự kết thúc xâu, theo qui ước kí tự có mã (tức '\0') vị trí mảng Khi đó, xâu dãy kí tự phần tử (thứ 0) đến kí tự kết thúc xâu (khơng kể kí tự cịn lại mảng)
Dưới hình ảnh minh họa cho xâu s1, s2, s3 Bảng 5.23: Xâu kí tự
0 // Vị trí phần tử mảng s1 H E L L O \0 A B // s1 = "HELLO"
s2 H E L L \0 O \0 A // s2 = "HELL" s3 \0 H E \0 L L O \0 // s3 = ""
(114)dành
cho
hội
đồng
nghiệm
thu
106 Mảng
thị xâu rỗng (chiếm ơ) Chú ý mảng kí tự khai báo với độ dài nhiên xâu chiếm số kí tự mảng tối đa kí tự
Lưu ý, kí tự viết cặp dấu nháy đơn, xâu bao cặp dấu nháy kép Ví dụ 'A' thể kí tự A, chiếm byte nhớ, "A" thể xâu kí tự A có độ dài (kí tự) lại chiếm bytes nhớ (cần thêm byte '\0' để kết thúc xâu)
5.3.1 Khai báo
Một xâu kí tự thực chất mảng kí tự, để khai báo xâu ta dùng mảng sau: char string_name [size] ; // không khởi tạo
char string_name [size] = string ; // có khởi tạo
char string_name [] = string ; // có khởi tạo
• Size kích thước mảng kí tự, với kích thước đủ để chứa xâu dài lên đến size - kí tự (1 kí tự cịn lại dành để lưu dấu kết thúc xâu) Độ dài thực xâu tính từ đầu mảng đến kí tự '\0' (không kể dấu này) Do vậy, để chứa xâu có độ dài tối đa n cần phải khai báo mảng s với kích thước n +
• Cách khai báo thứ hai có kèm theo khởi tạo xâu, dãy kí tự đặt cặp dấu nháy kép Ví dụ:
char name [26] ; // xâu họ tên chứa tối đa 25 kí tự
char course [31] = " Programming Language C++" ;
Xâu course chứa tối đa 30 kí tự, khởi tạo với nội dung "Programming Language C++" với độ dài thực 24 kí tự (chiếm 25 mảng char course[31])
• Cách khai báo thứ tự chương trình định độ dài mảng xâu khởi tạo (bằng độ dài xâu + 1) Ví dụ:
char month [] = " December " ; // độ dài mảng = 9
Cần phân biệt cách khai báo sau:
char month_1 [100] = " December " ; // và
char month_2 [100] = {'D', 'e', 'c', 'e', 'm', 'b', 'e', 'r'} ;
8 bytes hai mảng lưu nội dung dãy kí tự D-e-c-e-m-b-e-r, nhiên với cách khai báo (và khởi tạo) thứ chương trình tự động gán thêm kí tự '\0' vào bytes thứ month_1, cịn cách thứ khơng Nói cách khác, month_2 mảng kí tự đơn cịn month_1 xâu kí tự
5.3.2 Thao tác với xâu kí tự
Tương tự mảng liệu khác, xâu kí tự có đặc trưng mảng, nhiên chúng có điểm khác biệt Dưới điểm giống khác
(115)dành
cho
hội
đồng
nghiệm
thu
5.3 Lập trình thao tác với xâu kí tự 107
// chú ý kí tự ' phải viết \'
char str [50] = "I\'m a student " ;
cout << str [0] ; // in kí tự đầu tiên, tức kí tự 'I' str [1] = 'a' ; // đặt lại kí tự thứ 'a'
• Khơng thực phép toán trực tiếp xâu như:
// khai báo hai xâu str = "Hello" t
char str [20] = " Hello ", t[20] ;
t = " Hello " ; // sai, không gán mảng cho hằng t = str ; // sai, không gán hai mảng cho nhau
if (str < t) // sai, không so sánh hai mảng
• Tốn tử nhập liệu >> dùng có nhiều hạn chế Ví dụ char str [60] ;
cin >> str ; cout << str ;
Nếu xâu nhập vào "Tin học hóa" chẳng hạn tốn tử >> nhập "Tin" cho str (bỏ tất kí tự đứng sau dấu trắng), in hình có từ "Tin"
Vì phép tốn khơng dùng trực tiếp xâu nên chương trình dịch viết sẵn hàm thư viện khai báo file nguyên mẫu cstring Các hàm giải hầu hết công việc cần thao tác xâu Nó cung cấp cho NSD phương tiện để thao tác xâu gán, so sánh, chép, tính độ dài xâu, … Để sử dụng hàm đầu chương trình cần khai báo #include <cstring>
• Khi duyệt xâu, để nhận biết hết xâu dùng hai cách sau: – Kí tự xâu '\0'
– Kí tự đọc nằm vị trí strlen(str) (strlen() hàm trả lại độ dài xâu str). Đây vị trí kí tự '\0'
Ví dụ: Cần duyệt từ đầu đến cuối xâu str:
– for (int index = 0; index < strlen(str); index++) hoặc – for (int index = 0; str[index] != '\0'; index++)
5.3.3 Phương thức nhập xâu (#include <iostream>)
Do tốn tử nhập >> có hạn chế xâu kí tự, nên C++ đưa hàm riêng (cịn gọi phương thức) cin.getline(s, n) để nhập xâu kí tự Hàm có s xâu cần nhập nội dung n-1 số kí tự tối đa xâu Nếu xâu NSD nhập vào nhiều n-1 kí tự hàm lấy n–1 kí tự nhập để gán cho s
Ví dụ: Nhập ngày tháng dạng Mỹ (mm/dd/yy), đổi sang ngày tháng dạng Việt Nam (dd/mm/yy) in hình
(116)dành
cho
hội
đồng
nghiệm
thu
108 Mảng
4 using namespace std;
6 int main () {
8 char us_date [9] , vn_date [9] = " / / "; cout << " Enter the date in format mm/dd/yy: "; 10 cin getline (us_date , 9);
11 vn_date [0] = us_date [3]; vn_date [1] = us_date [4] ; // ngay 12 vn_date [3] = us_date [0]; vn_date [4] = us_date [1] ; // thang 13 vn_date [6] = us_date [6]; vn_date [7] = us_date [7] ; // nam 14 cout << "Date in American style : " << us_date << endl;
15 cout << "Date in Vietnamese style : " << vn_date << endl; 16
17 return 0; 18 }
Hình 5.24: Đổi ngày từ dạng Mỹ sang dạng Việt
5.3.4 Một số hàm làm việc với xâu kí tự (#include <cstring>)
Sao chép xâu
• strcpy(s, t): Gán nội dung xâu t cho xâu s (thay cho phép gán = không dùng) Hàm chép toàn nội dung xâu t (kể kí tự kết thúc xâu) vào xâu s Để sử dụng hàm này, cần đảm bảo độ dài mảng s độ dài mảng t Trong trường hợp ngược lại, kí tự kết thúc xâu khơng ghi vào s điều gây treo máy chạy chương trình
1 # include <iostream > # include <cstring >
4 using namespace std;
6 int main () {
8 char str_s [10] , str_t [10] ;
9 // str_t = "Face" ; // khong duoc
10 strcpy (str_t , "Face") ; // gan "Face" cho t 11 // str_s = str_t ; // khong duoc
12 strcpy (str_s , str_t ) ; // chep t sang s 13 cout << str_s << " to " << str_t << endl; // in ra: Face to Face 14 return 0;
15 }
Hình 5.25: Ví dụ strcpy
• strncpy(s, t, n): Sao chép n kí tự t vào s Hàm làm nhiệm vụ chép, khơng tự động gắn kí tự kết thúc xâu cho s Do NSD phải thêm câu lệnh đặt kí tự '\0' vào cuối xâu s sau chép xong
(117)dành
cho
hội
đồng
nghiệm
thu
5.3 Lập trình thao tác với xâu kí tự 109
3
4 using namespace std;
6 int main () {
8 char str_s [10] , str_t [10] = " Steven ";
9 strncpy (str_s , str_t , 5) ; // copy ki tu " Steve " vao s 10 str_s [5] = '\0 ' ; // dat dau ket thuc xau 11 cout << str_s << " is younger brother of " << str_t << endl;
12 // in : Steve is younger brother of Steven 13
14 return 0; 15 }
Hình 5.26: Ví dụ strncpy
Dạng tổng quát hàm là: strncpy(s + a, t + b, n) cho phép copy n kí tự vị trí (chỉ số) b xâu t đặt (ghi đè) vào xâu s vị trí a
1 # include <iostream > # include <cstring >
4 using namespace std;
6 int main () {
8 char us_date [12] = " 02/29/2015 "; char vn_date [12] = " / / ";
10 strncpy ( vn_date + 0, us_date + 3, 2) ; // ngay 11 strncpy ( vn_date + 3, us_date + 0, 2) ; // thang 12 strncpy ( vn_date + 6, us_date + 6, 4); // nam 13 cout << "Date in American style : " << us_date << endl; 14 cout << "Date in Vietnamese style : " << vn_date << endl; 15
16 return 0; 17 }
Hình 5.27: Chuyển đổi ngày strncpy Ghép hai xâu
• strcat(s, t): Nối t vào sau s (thay cho phép +) Hiển nhiên, hàm loại bỏ kí tự kết thúc xâu s trước nối thêm t Việc nối đảm bảo lấy kí tự kết thúc xâu t vào cho s (nếu s đủ chỗ) NSD khơng cần thêm kí tự vào cuối xâu Tuy nhiên, hàm không kiểm tra xem liệu độ dài s có đủ chỗ để nối thêm nội dung, việc kiểm tra phải NSD đảm nhiệm
1 # include <iostream > # include <cstring >
4 using namespace std;
(118)dành
cho
hội
đồng
nghiệm
thu
110 Mảng
8 char str_s [100] , str_t [100] = " Steve " ; strncpy (str_s , str_t , 3);
10 str_s [3] = '\0 '; // s = "Ste" 11 strcat (str_s , "p"); // s = "Step"
12 cout << str_t << " goes " << str_s << " by " << str_s << endl; 13 // Steve goes Step by Step 14
15 return 0; 16 }
Hình 5.28: Ví dụ strcat
• strncat(s, t, n): Nối n kí tự xâu t vào sau xâu s Hàm tự động đặt thêm dấu kết thúc xâu vào s sau nối xong (tương phản với strncpy()) Cũng giống strcat, hàm đòi hỏi độ dài s phải đủ chứa kết
1 # include <iostream > # include <cstring >
4 using namespace std;
6 int main () {
8 char str_s [20] = "Do be do" ;
9 char str_t [20] = "Steve is going home" ; 10 strcat (str_s , " to ");
11 strncat (str_s , str_t , 5);
12 cout << str_s << endl; // Do be to Steve 13
14 return 0; 15 }
Hình 5.29: Ví dụ strncat
Tương tự, strncpy, hàm strncat có dạng mở rộng strncat(s, t + b, n); tức nối n kí tự t kể từ vị trí b vào cho s
1 # include <iostream > # include <cstring >
4 using namespace std;
6 int main () {
8 char str_s [20] = "Do be to" ; char str_t [20] = "How are you ?" ; 10 strncat (str_s , str_t + 7, 4);
11 cout << str_s << endl; // Do be to you 12
13 return 0; 14 }
(119)dành
cho
hội
đồng
nghiệm
thu
5.3 Lập trình thao tác với xâu kí tự 111
So sánh hai xâu
• strcmp(s, t): Hàm so sánh xâu s t (thay cho phép toán so sánh) Giá trị trả lại hàm dấu hiệu kí tự khác s t Tức hiệu âm trả lại -1 (tương đương s1 < s2), hiệu dương trả lại giá trị (s1 > s2), so sánh đến hết xâu, tất hiệu (s1 == s2) trả lại Trong trường hợp quan tâm đến so sánh bằng, hàm trả lại giá trị hai xâu giá trị trả lại khác hai xâu khác (chú ý: xâu với độ dài ngắn không thiết xâu bé hơn)
1 # include <iostream > # include <cstring >
4 using namespace std;
6 int main () {
8 char str_s [20] = "Ha Noi" ; char str_t [20] = "Ha noi" ;
10 cout << " Result of comparison " << str_s << " and " << str_t << " is " <<
strcmp (str_s , str_t ) << endl; // -1
11 if ( strcmp (str_s , str_t ) < 0)
12 cout << str_s << " < " << str_t ; 13 else
14 cout << str_s << " > " << str_t ; 15 cout << endl;
16 return 0; 17 }
Hình 5.31: Ví dụ strcmp
• strncmp(s, t, n): Giống hàm strcmp(s, t) so sánh tối đa n kí tự hai xâu
1 # include <iostream > # include <cstring >
4 using namespace std;
6 int main () {
8 char str_s [20] = "Ha Noi" ; char str_t [20] = "Ha noi" ;
10 if ( strncmp (str_s , str_t , 2) == 0)
11 cout << "The first two characters of " << str_s << " and " << str_t << "
is the same" << endl;
12 else
13 cout << "The first two characters of " << str_s << " and " << str_t << "
is not the same" << endl;
14 cout << endl; 15 return 0; 16 }
(120)dành
cho
hội
đồng
nghiệm
thu
112 Mảng
• strcmpi(s, t): Như strcmp(s, t) khơng phân biệt chữ hoa, thường Khi "HA NOI" "Ha Noi" giống so sánh strcmpi
Lấy độ dài xâu
• strlen(s): Hàm trả giá trị độ dài xâu s Ví dụ: char str [10] = "Ha Noi" ;
cout << strlen (str) ; // 6
5.3.5 Các hàm chuyển đổi xâu dạng số thành số (#include <cstdlib>)
• atoi(str) ; atol(str) ; atof(str) ; (A-TO-I: alphabet to integer, )
Đối str xâu kí tự biểu thị cho giá trị số phù hợp Các hàm chuyển đổi str thành số nguyên (int), số nguyên dài (long) số thực (double) Trường hợp, xâu có chứa kí tự khác với chữ số, hàm chuyển đổi giá trị chữ số đứng trước kí tự lạ xâu Ví dụ:
int x = atoi("123"); // x = 123
double y = atof(" 12.345 "); // y = 12.345
atoi(" VND123 ") = 0; atoi("1$23") = 1; atoi("123%") = 123 atof("a12 345") = 0.0; atof("1a2 345") = 1.0;
atof(" 12.34 $5") = 12.34;
Chú ý kí tự 'e' 'E' hiểu số viết dạng dấu phẩy động, nên:atof("12.34e5") = 1.234E6 tức 1234000;
Trong ví dụ đây, sử dụng hàm atoi để tạo phiên new_atoi có tính chấp nhận chữ số có xâu để ghép thành số nguyên Hàm trả lại xâu không chứa chữ số Ví dụ: "$123" = 123, "a12$5b" = 125, "abc" =
1 # include <iostream > # include <cstdlib >
4 using namespace std;
6 const int MAX_SIZE = 10;
8 int New_Atoi (char input_str []) {
10 char output_str [ MAX_SIZE ]; 11 int num_digit = 0;
12 for (int index = 0; index < strlen ( input_str ); index ++) 13 {
14 if ('0' <= input_str [ index ] && input_str [ index ] <= '9') 15 output_str [ num_digit ++] = input_str [ index ] ;
16 }
17 output_str [ num_digit ] = '\0 '; 18 if ( num_digit > 0)
19 return atoi( output_str ); 20 else
(121)dành
cho
hội
đồng
nghiệm
thu
5.3 Lập trình thao tác với xâu kí tự 113
22 } 23 24
25 int main () 26 {
27 char str[ MAX_SIZE ]; 28 int res;
29 cout << " Enter string (less than or equal to characters ): "; 30 cin getline (str , MAX_SIZE );
31 res = New_Atoi (str); 32 cout << res << endl; 33 return 0;
34 }
Hình 5.33: Hàm new_atoi
Cách khai báo xâu dạng mảng cịn nhiều hạn chế, khó lập trình Xâu khai báo dạng trỏ kí tự đặc biệt C++ cung cấp lớp riêng xâu (lớp String) giúp người lập trình làm việc thuận lợi
5.3.6 Một số ví dụ làm việc với xâu
1 # include <iostream > # include <cstring >
4 using namespace std;
6 int main () {
8 const int MAX = 80; char input_str [MAX ]; 10 int num_char = 0;
11 cout << " Enter any string : "; 12 cin getline (input_str , MAX);
13 for (int index = 0; index < strlen ( input_str ); index ++) 14 if ( input_str [ index ] == 'a') num_char ++;
15 cout << " Number of characters 'a' that is belong in the string : " << num_char <<
endl ;
16 return 0; 17 }
Hình 5.34: Thống kê số chữ ’a’ xuất xâu s # include <iostream >
2 # include <cstring >
4 using namespace std;
6 const int MAX = 80;
8 int Str_Length (char str []) {
(122)dành
cho
hội
đồng
nghiệm
thu
114 Mảng
11 for (int index = 0; str[ index ] != '\0 '; index ++) 12 num_char ++;
13 return num_char ; 14 }
15
16 int main () 17 {
18 char input_str [MAX ];
19 cout << " Enter a string : "; 20 cin getline (input_str , MAX);
21 cout << " Length of the string = " << Str_Length ( input_str ) << endl ; 22 return 0;
23 }
Hình 5.35: Tính độ dài xâu cách đếm kí tự (tương đương với hàm strlen()) # include <iostream >
2 # include <cstring >
4 using namespace std; const int MAX = 80; const char BLANK = ' ';
8 int Get_Num_Words (char str []) {
10 int num_words = 0;
11 bool reading_blank = true; 12 int index = 0;
13 while (str[ index ] != '\0 ') 14 {
15 if ( reading_blank )
16 {
17 if (str[ index ] != BLANK )
18 {
19 num_words ++;
20 reading_blank = false;
21 }
22 }
23 else
24 {
25 if (str[ index ] == BLANK ) reading_blank = true;
26 }
27 index ++; 28 }
29 return num_words ; 30 }
31
32 int main () 33 {
34 char inp_str [MAX ];
35 cout << " Enter a string : "; 36 cin getline (inp_str , MAX); 37
(123)dành
cho
hội
đồng
nghiệm
thu
5.3 Lập trình thao tác với xâu kí tự 115
39
40 return 0; 41 }
Hình 5.36: Đếm số từ (qui ước dãy kí tự bất kỳ, liên tục) xâu Chương trình đọc từ đầu đến cuối xâu, sử dụng biến logic reading_blank để trạng thái đọc dấu cách hay kí tự khác Nếu trạng thái đọc dấu cách gặp kí tự khác tăng số từ lên đổi trạng thái Nếu trạng thái đọc kí tự khác gặp dấu cách đổi trạng thái, gặp kí tự khác khơng làm
1 # include <iostream > # include <cstring >
4 using namespace std; const int MAX = 80; const char BLANK = ' ';
7 const char END_OF_STRING = '\0';
9 int main () 10 {
11 char str[MAX ];
12 int head_flag , tail_flag ; 13
14 cout << " Enter a string : "; 15 cin getline (str , MAX); 16
17 head_flag = 0;
18 while (str[ head_flag ] == BLANK ) head_flag ++; 19 tail_flag = strlen (str) - 1;
20 while (str[ tail_flag ] == BLANK ) tail_flag ; 21
22 int new_len = tail_flag - head_flag - 1; 23 strncpy (str , str + head_flag , new_len ); 24 str[ new_len ] = END_OF_STRING ;
25
26 cout << "New String : \"" << str << "\"" << endl; 27
28 return 0; 29 }
Hình 5.37: Cắt dấu cách đầu xâu s Chương trình sử dụng cờ head_flag tail_flag chạy từ đầu xâu đến vị trí có kí tự khác dấu trắng Dùng hàm strncpy chép đoạn kí tự từ head_flag đến tail_flag vào lại đầu xâu, sau đặt dấu kết thúc
1 # include <iostream > # include <cstring > # include <conio h>
5 using namespace std;
6 const char END_OF_STRING = '\0'; const char key [11] = " HaNoi2000 ";
(124)dành
cho
hội
đồng
nghiệm
thu
116 Mảng
10 {
11 char password [11];
12 int num_times = 1; // cho phep nhap lan dau 13 cout << " Enter password :\n";
14 do {
15 int index = 0;
16 while (( password [index ] = getch ()) != 13 && ++ index <= 10) cout << "X" ;
// 13 = enter
17 cout << "\n" ;
18 password [ index ] = END_OF_STRING ; 19 if ( ! strcmp (password , key))
20 {
21 cout << " Correct Password Continue , please \n" ;
22 break;
23 }
24 else cout << " Incorrect Password " ; 25 num_times ++;
26 if ( num_times <= 3) 27 cout << " Reenter \n";
28 else
29 cout << "You don 't be permitted to use this software \n"; 30 } while ( num_times <= 3);
31
32 return 0; 33 }
(125)dành
cho
hội
đồng
nghiệm
thu
5.3 Lập trình thao tác với xâu kí tự 117
Bài tập
1 Hãy nhập vào 16 số nguyên In thành dòng, cột
2 Nhập vào dãy n số thực Tính tổng dãy, trung bình dãy, tổng số âm, dương tổng số vị trí chẵn, vị trí lẻ dãy Tìm phần tử gần số trung bình dãy
3 Nhập vào dãy n số Hãy in số lớn nhất, bé dãy Tìm vị trí xuất phần tử x dãy Nhập vào dãy số In dãy xếp tăng dần, giảm dần
6 Cho dãy tăng dần Chèn thêm vào dãy phần tử x cho dãy xếp tăng dần
7 Cho dãy số xếp tăng dần Không dùng mảng phụ, viết chương trình in dãy trộn dãy (thành dãy) theo thứ tự xếp tăng dần
8 Cho hai dãy a, b Kiểm tra a có dãy b hay khơng ? (a dãy b ta bỏ bớt số phần tử b thu dãy a Ví dụ 1, 3, dãy 3, 4, 1, 2, 3, 7, 8, 5, 0,
9 Câu khẳng định sau sai :
(a) Các phần tử mảng xếp liên tục nhớ (b) Các phần tử mảng chiếm số byte
(c) Với mảng int x[3][4] số nguyên cuối phần tử x[3][4] (d) Cho phép khởi tạo giá trị mảng khai báo
10 Xét khởi tạo :
int a [2][3] = {{1, 2}, {3, 4, 5}} ;
int b [2][3] = {1, 2, 3, 4, 5} ;
Câu sau sai ?
(a) Cách khởi tạo mảng b phép
(b) a[i][j] = b[i][j] (i = 0,1; j = 0,1,2) (c) Hai mảng a, b có số phần tử
(d) Các phần tử chưa khởi tạo a, b nhận giá trị 11 Cho ma trận nguyên kích thước m*n Tính:
(a) Tổng tất phần tử ma trận
(b) Tổng tất phần tử dương ma trận (c) Tổng tất phần tử âm ma trận (d) Tổng tất phần tử chẵn ma trận
(126)dành
cho
hội
đồng
nghiệm
thu
118 Mảng
12 Cho ma trận thực kích thước m*n Tìm:
(a) Số nhỏ nhất, lớn (kèm số) ma trận
(b) Số nhỏ nhất, lớn (kèm số) hàng ma trận (c) Số nhỏ nhất, lớn (kèm số) cột ma trận
(d) Số nhỏ nhất, lớn (kèm số) đường chéo ma trận (e) Số nhỏ nhất, lớn (kèm số) đường chéo phụ ma trận
13 Giả sử có mảng số thực A(m, n) Một phần tử gọi điểm yên ngựa phần tử nhỏ hàng lớn cột Viết chương trình nhập ma trận, in phần tử yên ngựa số (nếu có)
14 Giả sử có khai báo char str[12] = "abcdef" Câu sau sai: (a) str[6] = '\0'
(b) Độ dài str (c) str mảng kí tự (d) str xâu kí tự
15 Cho khai báo char a[8]; Để gán giá trị “tin hoc” cho a, sinh viên viết theo cách khác nhau: sinh viên 1: a = "tin hoc"; sinh viên : strcpy(a, "tin hoc") Chọn câu câu sau:
(a) sinh viên (b) sinh viên
(c) sinh viên (d) sinh viên sai
16 Xét khởi tạo : char x[] = {'C', 'N', 'T', 'T' } char y[] = "CNTT" Chọn câu khẳng định sau:
(a) Cả hai khởi tạo không hợp lệ (b) x y xâu kí tự
(c) Chỉ có khởi tạo x phép
(d) Số phần tử mảng khởi tạo x y khác 17 Đọc xâu vào, in "dung" xâu vào "Ha Noi" Ngược lại in "sai"
18 Nhập xâu Không phân biệt viết hoa hay viết thường, in kí tự có mặt xâu số lần xuất (ví dụ xâu "Trach - Van - Doanh" có chữ 'a' xuất lần, c(1 lần), d(1), h(2), n(2), o(1), r(1), t(1), -(2), space(4))
(127)dành
cho
hội
đồng
nghiệm
thu
Chương 6
Các kiểu liệu trừu tượng
6.1 Kiểu liệu trừu tượng cấu trúc (struct)
Trong chương trước, ta thấy để lưu trữ giá trị gồm nhiều thành phần liệu giống ta sử dụng kiểu mảng Tuy nhiên, thực tế nhiều liệu tập kiểu liệu khác tập hợp lại, ví dụ lý lịch người gồm nhiều kiểu liệu khác họ tên, tuổi, giới tính, mức lương … để quản lý liệu kiểu C++ đưa kiểu liệu cấu trúc
Kiểu cấu trúc giống kiểu mảng chỗ quản lý tập hợp liệu chia thành thành phần Các thành phần kiểu mảng truy cập thơng qua số, cịn thành phần kiểu cấu trúc (còn gọi trường) truy cập thơng qua tên gọi thành phần Điểm giống khác kiểu mảng cấu trúc thành phần lưu trữ liên tiếp nhớ, nhiên số bytes thành phần kiểu cấu trúc khác nhau, khác với kiểu mảng độ dài thành phần giống chúng có kiểu Ví dụ, chương trình quản lý điểm tốt nghiệp sinh viên, sinh viên đối tượng mà có thành phần liệu cần phải có là: họ tên, năm sinh, điểm tốt nghiệp Để quản lý đối tượng sinh viên ta xây dựng kiểu cấu trúc sau:
struct Student {
char name [30];
int birth_year ;
double mark; };
Lưu ý, Student gọi thẻ tên (identifier_tag) kiểu cấu trúc tên biến Để đơn giản ta gọi kiểu cầu trúc Student hay ngắn gọn kiểu Student (như kiểu chuẩn int, double, bool, … ) Trong kiểu Student có chứa thành phần với kiểu khác : xâu kí tự, số nguyên số thực tương ứng với tên thành phần là: name, birth_year, mark
Thông thường, kiểu cấu trúc hay dùng chung cho hàm nên phần lớn chúng khai báo kiểu tồn cục Tóm lại, việc xây dựng thẻ tên kiểu cấu trúc hay kiểu cấu trúc tuân theo cú pháp sau
(128)dành
cho
hội
đồng
nghiệm
thu
120 Các kiểu liệu trừu tượng
struct identifier_tag {
list of members ; } var_list ;
• Mỗi thành phần (member) giống biến riêng kiểu, gồm kiểu tên thành phần Một thành phần cịn gọi trường (field)
• Phần tên (tag) kiểu cấu trúc phần danh sách biến có khơng Tuy nhiên khai báo kí tự kết thúc cuối phải dấu chấm phẩy (;)
• Các kiểu cấu trúc phép khai báo lồng nhau, nghĩa thành phần kiểu cấu trúc lại trường có kiểu cấu trúc khác
• Một biến có kiểu cấu trúc phân bố nhớ cho thành phần kề liên tục theo thứ tự xuất khai báo
• Khai báo biến kiểu cấu trúc giống khai báo biến kiểu sở dạng: struct identifier_tag list_of_var ; // kiểu cũ C
hoặc theo C++ bỏ qua từ khóa struct:
identifier_tag list_of_var ; // trong C++
Các biến khai báo kèm khởi tạo:
identifier_tag var1 = { created_value }, var2 , ;
Ví dụ khai báo kiểu Student: struct Student
{
char name [30];
int birth_year ;
double mark;
} monitor = { “Nguyen Van ”Anh , 1992 , 8.7} , x;
Trong khai báo trên, ta đồng thời khai báo biến kiểu Student monitor (lớp trưởng) x, x chưa khởi tạo monitor khởi tạo với họ tên Nguyễn Vân Anh, sinh năm 1992 điểm tốt nghiệp 8.7 Khi cần khai báo thêm biến có kiểu Student, theo cú pháp, ví dụ:
Student vice_monitor , K58 [60] , y;
Trong khai báo trên, vice_monitor, y biến đơn, K58 mảng mà thành phần sinh viên, ví dụ dùng để biểu diễn liệu lớp học
Ưu điểm kiểu cấu trúc dùng để biểu diễn tập giá trị khác kiểu, nhiên với tập giá trị kiểu hiển nhiên biểu diễn kiểu cấu trúc nhiều trường hợp ý nghĩa đối tượng rõ ràng so với biểu diễn kiểu mảng
(129)dành
cho
hội
đồng
nghiệm
thu
6.1 Kiểu liệu trừu tượng cấu trúc (struct) 121
struct Fraction {
int numerator ;
int denomirator ; } ;
Với cách biểu diễn này, thành phần tử mẫu phân số đặt tên thay phải ngầm định cách biểu diễn dạng mảng Tương tự, ngày tháng khai báo :
struct Date {
int day ;
int month ;
int year ;
} holiday = { 1, 5, 2000 } ;
một biến holiday khai báo kèm kiểu khởi tạo số 1, 5, 2000 Các giá trị khởi tạo gán cho thành phần theo thứ tự khai báo, tức day = 1, month = year = 2000
Vì thành phần day, month, year kiểu int nên giống khai báo loại biến khác, chúng gộp dịng (vẫn giữ thứ tự):
struct Date {
int day , month , year ; } holiday = { 1, 5, 2000 } ;
Kiểu cấu trúc chứa thành phần kiểu cấu trúc Ví dụ, kiểu sinh viên khai báo trên, ta thay trường năm sinh (birth_year) trường có chứa ngày tháng năm sinh như:
struct Student {
char name [30]; Date birthday ;
double mark;
} monitor = { " Nguyen Van Anh", {1, 1, 1992} , 8.7} , x;
thành phần birthday Student có kiểu Date cấu trúc, cách khởi tạo giá trị cho biến monitor thay đổi cho phù hợp từ việc khởi tạo 1992 cho thành phần birth_year khai báo cũ thành {1, 1, 1992} cho trường birthday khai báo
Kiểu cấu trúc Class dùng chứa thông tin lớp học gồm tên lớp, danh sách sinh viên ví dụ minh họa cho việc kết hợp loại kiểu khác kiểu
struct Class {
char name [10] , // xâu kí tự Student list[MAX ]; // mảng cấu trúc } ;
Tên thành phần phép trùng cấu trúc khác nhau, ví dụ name xuất hai cấu trúc Student Class
(130)dành
cho
hội
đồng
nghiệm
thu
122 Các kiểu liệu trừu tượng
Tuy nhiên, may mắn so với cách làm việc mảng, cấu trúc phép gán (=) giá trị cho cách trực tiếp, mảng gán thành phần Phép gán trực tiếp tương đương với việc gán thành phần cấu trúc Ví dụ:
Student monitor = { "NVA", { 1, 1, 1992 }, 5.0 }; Student good_stu ;
good_stu = monitor ;
Chú ý: không gán giá trị cụ thể cho biến cấu trúc Cách gán thực khởi tạo Ví dụ:
Student good_stu ;
good_stu = { "NVA", { 1, 1, 1992 }, 5.0 }; // sai
1 # include <iostream > # include <iomanip > using namespace std;
5 int main () {
7 const int NUM_OF_STUDENTS = 3; struct Date
9 {
10 int day , month , year ; 11 };
12 struct Student 13 {
14 char name [30]; 15 Date birthday ; 16 double mark; 17 };
18 Student monitor = { "Bill Gate", { 1, 11, 2001 }, 5.0 }; 19 Student other_student ;
20 other_student = monitor ;
21 other_student birthday year = 2002; // Dat lai nam sinh 22
23 cout << "Name : " << other_student name << endl;
24 cout << " Birthday : " << other_student birthday day << "/" << other_student
birthday month << "/" << other_student birthday year << endl;
25 cout << "Mark: " << other_student mark << endl; 26
27
28 return 0; 29 }
Hình 6.1: Gán biến cấu trúc
Chú ý: Mặc dù hai cấu trúc gán cho (=) chúng lại không so sánh (==) với nhau, ta phải so sánh thành phần chúng
6.1.2 Hàm cấu trúc
Đối hàm cấu trúc
(131)dành
cho
hội
đồng
nghiệm
thu
6.1 Kiểu liệu trừu tượng cấu trúc (struct) 123
• Là biến cấu trúc, giá trị truyền cấu trúc • Là tham chiếu cấu trúc, giá trị truyền cấu trúc
• Là trỏ cấu trúc, giá trị truyền địa cấu trúc Dạng truyền theo dẫn trỏ trình bày chương giáo trình
Nhìn chung, cấu trúc kiểu liệu lớn, chiếm nhiều nhớ nhiều thời gian chép nên dạng truyền theo tham chiếu sử dụng thường xuyên truyền theo giá trị, Để tránh thay đổi giá trị biến thường ta khai báo tham đối kiểu tham chiếu dạng const
Ví dụ sau cho phép tính xác khoảng cách ngày tháng thứ ngày tháng Về mặt liệu, kiểu ngày tháng (Date) khai báo dạng toàn cục Mảng int NUM_DAYS[13] cung cấp số ngày cố định tháng (NUM_DAYS[i] số ngày tháng i), tháng xem 28 ngày, gặp năm nhuận số ngày tháng cộng thêm
Chương trình gồm hàm:
• int bissextile_year(int year): Hàm trả lại đối year năm nhuận ngược lại Năm nhuận năm chia hết cho không chia hết cho 100, nhiên chia hết cho 400 năm lại nhuận
• int num_days_of_month(int month, int year): Hàm trả lại số ngày tháng (month) năm year, đơn giản hàm lấy liệu từ mảng NUM_DAYS cho sẵn month = year năm nhuận cộng thêm
• long DtoN(Date date) (Date to Numeric) Hàm chuyển tương đương ngày tháng thành số nguyên dài số ngày tính từ 1/1/1 đến date Về mặt thuật tốn, hàm tính số ngày qua từ năm năm year – Mỗi năm cộng thêm 365 366 ngày Tiếp theo cộng thêm số ngày từ tháng đến tháng month – năm (số ngày tháng lấy thông qua hàm num_days_of_month) cuối cộng thêm số ngày (day)
• Date NtoD(long n) (Numeric to Date) Hàm chuyển tương đương số nguyên thành ngày tháng (quan niệm số nguyên số ngày tính từ 1/1/1 đến số ngày cần chuyển) Về mặt thuật toán, hàm trừ dần 365 366 ngày để tính tăng lên năm, số ngày lại (bé 365 366) chuyển sang tháng ngày theo cách tương tự
• long Distance_Dates(Date date1, Date date2): Hàm trả lại khoảng cách hai ngày tháng, đơn giản là: DtoN(date1) - DtoN(date2);
• Để tính thứ date, hàm chọn ngày biết thứ (ví dụ ngày 1/1/2000 biết thứ bảy) lấy khoảng cách với date Do thứ lặp lại theo chu kỳ ngày nên khoảng cách chia hết cho (phần dư 0) thứ date thứ ngày biết, dựa phần dư khoảng cách với ta suy đốn thứ date Trong hình chương trình output
(132)dành
cho
hội
đồng
nghiệm
thu
124 Các kiểu liệu trừu tượng
4 // number of days of months
5 const int NUM_DAYS [13] = {0 ,31 ,28 ,31 ,30 ,31 ,30 ,31 ,31 ,30 ,31 ,30 ,31}; struct Date
7 {
8 int day , month , year ; };
10
11 // - Func returns if year is bissextile_year and if not 12 int Bissextile_year (int year)
13 {
14 return (year %4 == && year %100 != || year %400 == 0)? : 0; 15 }
16
17 // - Func returns number of days of any month 18 // - (plus if year is bissextile )
19 int Num_Days_Of_Month (int month , int year) 20 {
21 return NUM_DAYS [ month ] + (( month == 2) ? Bissextile_year (year) : 0); 22 }
23
24 // - Func returns total number of days from 1/1/1 to the date 25 long DtoN(Date date) // DtoN: Date to Numeric
26 {
27 long res = 0;
28 for (int index = 1; index < date.year; index ++) 29 res += 365 + Bissextile_year ( index );
30 for (int index = 1; index < date month ; index ++) 31 res += num_days_of_month (index , date.year); 32 res += date.day;
33 return res; 34 }
35
36 // - Func returns a date that corresponds to a numeric 37 Date NtoD(long num_days ) // NtoD: Nummeric to Date 38 {
39 Date res; 40 res.year = 1;
41 while ( num_days > 365 + Bissextile_year (res.year)) 42 {
43 num_days -= 365 + Bissextile_year (res.year); 44 res.year ++;
45 }
46 res.month = 1;
47 while ( num_days > num_days_of_month (res.month , res.year)) 48 {
49 num_days -= num_days_of_month (res.month ,res.year); 50 res month ++;
51 }
52 res.day = num_days ; 53 return res;
54 } 55
(133)dành
cho
hội
đồng
nghiệm
thu
6.1 Kiểu liệu trừu tượng cấu trúc (struct) 125
58 {
59 return DtoN( date1 ) - DtoN( date2 ); 60 }
61
62 // - Func returns day of week of any date
63 void DoW(Date date , char dow []) // DoW: Day of week 64 {
65 Date curdate = {1, 1, 2000}; // the date 1/1/2000 is Sartuday 66 long dist = Distance_Dates (date , curdate );
67 int odd = dist % 7; 68 if (odd < 0) odd += 7; 69 switch (odd) {
70 case 0: strcpy (dow , " Sartuday "); break; 71 case 1: strcpy (dow , " Sunday "); break; 72 case 2: strcpy (dow , " Monday "); break; 73 case 3: strcpy (dow , " Tuesday "); break; 74 case 4: strcpy (dow , " Wednesday "); break; 75 case 5: strcpy (dow , " Thursday "); break; 76 case 6: strcpy (dow , " Friday "); break; 77 }
78 } 79
80 // /////////////////////////////////////////////////////////////////////// 81 int main ()
82 {
83 Date your_birthday , today ;
84 char dow_birthday [10] , dow_today [10];
85 cout << "What date is your birthday ? (dd/mm/yyyy) " ;
86 cin >> your_birthday day >> your_birthday month >> your_birthday year ; 87 cout << "And today ? (dd/mm/yyyy) " ;
88 cin >> today day >> today month >> today year ; 89 DoW( your_birthday , dow_birthday );
90 DoW(today , dow_today );
91 cout << "You were born on " << dow_birthday << " Today is " << dow_today << endl
;
92 cout << "You were born " << Distance_Dates (today , your_birthday ) << " days ago."
<< endl;
93
94 return 0; 95 }
Hình 6.2: Khoảng cách ngày Giá trị hàm cấu trúc
Khác với kiểu mảng, hàm trả lại giá trị cấu trúc Từ đó, ta viết lại chương trình tính cộng, trừ, nhân chia hai phân số, phép tốn hàm với đối cấu trúc phân số kết trả lại hàm kết phép toán cấu trúc phân số chương trình
1 # include <iostream > using namespace std;
(134)dành
cho
hội
đồng
nghiệm
thu
126 Các kiểu liệu trừu tượng
6 int numerator ; int denomirator ; };
9
10 void Display ( Fraction a, Fraction b, Fraction c, char op) 11 {
12 cout << a numerator << "/" << a denomirator ; cout << " " << op << " "; 13 cout << b numerator << "/" << b denomirator ; cout << " = ";
14 cout << c numerator << "/" << c denomirator << endl; 15 }
16
17 Fraction Add( Fraction a, Fraction b) 18 {
19 Fraction c;
20 c numerator = a numerator *b denomirator + a denomirator *b numerator ; 21 c denomirator = a denomirator *b denomirator ;
22 return c; 23 }
24
25 Fraction Sub( Fraction a, Fraction b) 26 {
27 Fraction c;
28 c numerator = a numerator *b denomirator - a denomirator *b numerator ; 29 c denomirator = a denomirator *b denomirator ;
30 return c; 31 }
32
33 Fraction Product ( Fraction a, Fraction b) 34 {
35 Fraction c;
36 c numerator = a numerator *b numerator ;
37 c denomirator = a denomirator *b denomirator ; 38 return c;
39 } 40
41 Fraction Divide ( Fraction a, Fraction b) 42 {
43 Fraction c;
44 c numerator = a numerator *b denomirator ; 45 c denomirator = a denomirator *b numerator ; 46 return c;
47 } 48
49 int main () 50 {
51 Fraction a, b, c;
52 cout << " Enter fraction #1: " << endl ; 53 cout << "\ tnumerator : "; cin >> a numerator ; 54 cout << "\ tdenomirator : "; cin >> a denomirator ; 55 cout << " Enter fraction #2: " << endl ;
56 cout << "\ tnumerator : "; cin >> b numerator ; 57 cout << "\ tdenomirator : "; cin >> b denomirator ; 58 cout << " Results :\n";
(135)dành
cho
hội
đồng
nghiệm
thu
6.1 Kiểu liệu trừu tượng cấu trúc (struct) 127
60 Display (a, b, c, '+');
61 c = Sub(a, b); // Compute and display a - b 62 Display (a, b, c, '-');
63 c = Product (a, b); // Compute and display a * b 64 Display (a, b, c, '*');
65 c = Divide (a, b); // Compute and display a / b 66 Display (a, b, c, ':');
67
68 return 0; 69 }
Hình 6.3: Phân số
Với kiến thức trang bị đến thời điểm này, người đọc viết chương trình nhỏ tương đối hồn chỉnh tốn quản lý sinh viên mục
6.1.3 Bài toán Quản lý sinh viên (QLSV)
Bài toán QLSV trình bày ví dụ nhỏ việc tổng hợp đặc trưng lập trình kiến thức NNLT C/C++ đến thời điểm Bài toán đặt việc quản lý danh sách sinh viên với chức chủ yếu làm việc với danh sách là: Tạo, Xem, Xóa, Sửa, Bổ sung, Chèn, Sắp xếp Thống kê Trong mục này, chương trình xây dựng để minh họa liệu có kiểu cấu trúc (sinh viên) mảng cấu trúc (danh sách sinh viên) Các phiên với kiểu lớp trình bày mục 6.2 với danh sách liên kết trình bày chương Mã đầy đủ các phiên cho phụ lục A.1, A.2, A.3.
Cấu trúc liệu
Thông tin sinh viên tổ chức dạng cấu trúc Danh sách sinh viên mảng cấu trúc khai báo:
1 const int MAXSIZE_OF_LIST = 60;
2 struct Date { int day , month , year ; };
3 struct Student { char name [30]; Date birthday ; int sex; double mark; }; Student List[ MAXSIZE_OF_LIST ] ; // Danh sach sinh vien int num_students ; // So luong sinh vien
Hình 6.4: Khai báo cấu trúc sinh viên
Vì danh sách sinh viên liệu dùng chung hàm nên khai báo đặt bên tất hàm (toàn cục)
Chức năng
Mỗi chức thực thơng qua hàm chương trình Chương trình gồm hàm sau:
1 /* Nhom ham chinh , lam viec voi danh sach */
2 void Make_List (); // Tao danh sach
3 void Display_List (); // Hien danh sach void Update_List (); // Cap nhat danh sach void Insert_List (); // Chem them vao danh sach void Append_List (); // Bo sung vao cuoi danh sach void Remove_List (); // Xoa khoi danh sach
(136)dành
cho
hội
đồng
nghiệm
thu
128 Các kiểu liệu trừu tượng
9 void Count_List (); // Thong ke danh sach 10 /* Nhom ham lam viec voi mot sinh vien */
11 Student New_Student (); // Tao mot sinh vien moi 12 void Display_Student ( Student x); // Hien thi mot sinh vien 13 void Update_Student ( Student &x); // Cap nhat sinh vien 14 /* Nhom ham phuc vu */
15 void Set_List (); // Dung de test chuong trinh 16 void Swap( Student &x, Student &y);
17 void Get_Firstname (const char name [], char firstname []); // Lay ten cua hoten 18 void Sort_List_by_Name (); // Sap xep tang theo ho ten 19 void Sort_List_by_Mark (); // Sap xep giam theo diem
Hình 6.5: Danh sách hàm chương trình QLSV
Do danh sách sinh viên khai báo toàn cục nên hầu hết hàm thao tác trực tiếp với danh sách, khơng có đối không cần giá trị trả lại
Giao diện
Hàm main() tạo menu cho phép NSD chọn chức chọn để chấm dứt chương trình
1 int main () {
3 Set_List (); int choice ; do {
6 system ("CLS");
7 cout << "\n ============= MAIN MENU ( Struct ver ) =============\ n\n"; cout << "1: Make List of students \n"; cout << "2: Display List of students \n"; 10 cout << "3: Update a student of the List \n"; 11 cout << "4: Insert a student to the List \n"; 12 cout << "5: Append a student to the List \n"; 13 cout << "6: Remove a student from the List \n"; 14 cout << "7: Sort List of students \n"; 15 cout << "8: Count number of Male/ Female students \n";
16 cout << "0: Exit \n";
17 cout << "\n ===================================================\ n"; 18 cout << "\ nYour choice ? " ;
19 cin >> choice ; cin ignore (); 20 system ("CLS");
21 switch ( choice )
22 {
23 case 1: Make_List () ; break; 24 case 2: Display_List () ; break; 25 case 3: Update_List () ; break; 26 case 4: Insert_List () ; break; 27 case 5: Append_List () ; break; 28 case 6: Remove_List () ; break; 29 case 7: Sort_List () ; break; 30 case 8: Count_List () ; break;
31 }
32 } while ( choice ) ; 33 return 0 ;
(137)dành
cho
hội
đồng
nghiệm
thu
6.1 Kiểu liệu trừu tượng cấu trúc (struct) 129
Hình 6.6: Hàm main() chương trình QLSV
Nếu NSD chọn MAIN MENU, hàm main() gọi hàm Make_List() để thực chức tạo lập danh sách Để nhập danh sách, hàm lập vòng lặp từ đến số sinh viên (biến num_students), lần lặp hàm gọi đến hàm New_student() để nhập liệu cho sinh viên New_student() có giá trị trả lại cấu trúc sinh viên vừa nhập Dưới mã hàm Make_List() New_student()
1 void Make_List () {
3 cout << " Enter the number of students : "; cin >> num_students ;
5 for (int index = 0; index < num_students ; index ++)
6 {
7 cout << " Enter data values of student #" << index +1 << ":\n"; List[ index ] = New_student () ;
9 }
10 } 11
12 Student New_Student () 13 {
14 Student x ; 15 fflush ( stdin ) ;
16 cout << " Full name: "; cin getline (x.name , 30) ;
17 cout << " Birthday : "; cin >> x birthday day >> x birthday month >> x birthday
.year ;
18 cout << " Sex (0: Male , 1: Female ): "; cin >> (x.sex) ; 19 cout << " Mark: "; cin >> (x.mark) ;
20 return x; 21 }
Hình 6.7: Hàm Make_List() New_student()
Chức xem danh sách thực hàm Display_List() cách duyệt từ đến số sinh viên lần lặp gọi hàm Display_student() để thông tin sinh viên void Display_List ()
2 {
3 int index ;
4 cout << "\ nLIST OF STUDENTS " << endl ;
5 for (int index = 0; index < num_students ; index ++)
6 {
7 cout << index +1 << " " ; Display_student (List[index ]) ;
9 }
10 } 11
12 void Display_Student ( Student x) 13 {
14 cout << x.name << "\t" ;
15 cout << setw (2) << x birthday day << "/" << setw (2) << x birthday month << "/" <<
setw (2) << x birthday year << "\t" ;
(138)dành
cho
hội
đồng
nghiệm
thu
130 Các kiểu liệu trừu tượng
17 cout << "Male" << "\t" ; 18 else
19 cout << " Female " << "\t" ; 20 cout << x.mark;
21 cout << endl; 22 }
Hình 6.8: Hàm Display_List() Display_student()
Chức sửa Update_List() yêu cầu NSD nhập sinh viên cần sửa (thơng qua số thứ tự) sau gọi hàm Update_Student() để sửa thông tin cho sinh viên Do kích thước cấu trúc thường lớn nên để tiết kiệm nhớ thời gian chép, đối hàm Update_Student() khai báo dạng tham chiếu Để đảm bảo an toàn, tránh nhầm lẫn, hàm cho NSD sửa sinh viên, sau NSD xác nhận thông tin sửa xác hàm cập nhật vào danh sách
1 void Update_List () {
3 int order ;
4 cout << " Select the order of student : " ; cin >> order ; cin ignore ();
6 Update_Student (List[order -1]) ; }
8
9 void Update_Student ( Student &x) 10 {
11 Student copy = x; 12 Display_Student (copy); 13 int choice ;
14 do {
15 cout << "1: Name" << endl ; 16 cout << "2: Birthday " << endl ; 17 cout << "3: Sex" << endl ; 18 cout << "4: Mark" << endl ; 19 cout << "0: Exit" << endl ;
20 cout << " Select member to update ? " ; 21 cin >> choice ; cin ignore ();
22 switch ( choice )
23 {
24 case 1: cout << " Enter new name: " ; cin getline (copy.name , 30) ; break; 25 case 2: cout << " Enter new birthday : " ; cin >> copy birthday day >> copy
birthday month >> copy birthday year ; break;
26 case 3: cout << " Enter new sex: " ; cin >> copy.sex ; break; 27 case 4: cout << " Enter new mark: " ; cin >> copy.mark ; break;
28 }
29 } while ( choice ) ; 30 int sure ;
31 cout << "Are you sure for updating (1: sure , 0: not sure) ? " ; cin >> sure ; 32 if (sure)
33 {
34 x = copy ;
35 cout << "\ nInformation of student was updated \n"; 36 }
(139)dành
cho
hội
đồng
nghiệm
thu
6.1 Kiểu liệu trừu tượng cấu trúc (struct) 131
38 39 }
Hình 6.9: Hàm Update_List() Update_Student()
Hàm Insert_List() cho phép chèn sinh viên vào danh sách vị trí pos cách đẩy sinh viên từ vị trí tiến lên để dành trống pos cho sinh viên vừa tạo (bởi hàm New_student())
1 void Insert_List () {
3 cout << " Enter data of new student :\n" ; Student new_student = New_Student () ; int pos;
6 cout << " Enter position of new student : " ; cin >> pos; for (int index = num_students -1; index > pos; index ) List[ index + 1] = List[ index ];
9 List[pos -1] = new_student ; 10 num_students ++ ;
11 cout << "The student is inserted to the List\n" ; 12
13 }
Hình 6.10: Hàm Insert_List()
Để bổ sung vào cuối danh sách sinh viên ta gọi hàm Append_List(), hàm gọi đến hàm New_student() để tạo sinh viên trước ghép vào danh sách
1 void Append_List () {
3 cout << " Enter data of new student :\n" ; List[ num_students ] = New_student () ; num_students ++ ;
6 cout << "The student is appended to the List\n" ;
8 }
Hình 6.11: Hàm Append_List()
Chức xóa thực qua hàm Remove_List(), hàm cho phép NSD chọn sinh viên cần xóa thơng qua số thứ tự sinh viên danh sách Thực chất việc xóa dồn sinh viên phía sau sinh viên cần xóa phía trước để lấp đầy vị trí sinh viên bị xóa void Remove_List ()
2 {
3 Display_List (); int order ;
5 cout << " Select the order of student for removing : " ; cin >> order ; cin ignore ();
7 for (int index = order ; index < num_students ; index ++) List[ index - 1] = List[ index ];
9 num_students ;
10 cout << "The student is removed from the List\n" ; 11
(140)dành
cho
hội
đồng
nghiệm
thu
132 Các kiểu liệu trừu tượng
Hình 6.12: Hàm Remove_List()
Hàm Sort_List() hai hàm “con” Sort_List_by_Name() Sort_List_by_Mark() thực chức xếp tăng dần theo họ tên giảm dần theo điểm số sinh viên thuật toán xếp chọn Hàm cho phép NSD chọn cách xếp Để tráo đổi cấu trúc, hàm gọi đến hàm swap() Để theo tên, hàm gọi đến hàm phục vụ Get_firstname(const char name[], char firstname[]) hàm trả lại xâu tên sinh viên vào tham đối firstname hàm Thực chất, hàm xếp theo tên tên trùng hàm họ cách so sánh tên + họ sinh viên với Trong chương trình ta ngầm định họ tên viết dạng chuẩn: không chứa dấu cách hai đầu họ tên
1 void Sort_List () {
3 int choice ; cout << endl;
5 cout << " -\n" ; cout << "1: Sort List increasing by name" << endl ;
7 cout << "2: Sort List decreasing by mark" << endl ;
8 cout << " -\n" ; cout << "Your choice ? " ;
10 cin >> choice ; cin ignore (); 11 switch ( choice )
12 {
13 case 1: Sort_List_by_Name () ; break; 14 case 2: Sort_List_by_Mark () ; break; 15 }
16 if ( choice == 1)
17 cout << "List of students is sorted increasing by name\n"; 18 else if ( choice == 2)
19 cout << "List of students is sorted decreasing by mark\n" ; 20 else
21 cout << "List of students is not sorted \n" ; 22
23 } 24
25 void Sort_List_by_Name () // increasing on name 26 {
27 int i, j;
28 for (i = 0; i < num_students - 1; i++) 29 for (j = i+1; j < num_students ; j++)
30 {
31 char fn1 [40] , fn2 [40];
32 Get_Firstname (List[i] name , fn1) ; strcat (fn1 , List[i] name) ; 33 Get_Firstname (List[j] name , fn2) ; strcat (fn2 , List[j] name) ; 34 if ( strcmp (fn1 , fn2) > 0)
35 Swap(List[i], List[j]) ;
36 }
37 } 38
39 void Sort_List_by_Mark () // decreasing on mark 40 {
(141)dành
cho
hội
đồng
nghiệm
thu
6.1 Kiểu liệu trừu tượng cấu trúc (struct) 133
42 for (i = 0; i < num_students - 1; i++) 43 for (j = i+1; j < num_students ; j++) 44 if (List[i] mark < List[j] mark) 45 Swap(List[i], List[j]) ; 46 }
47
48 void Get_Firstname (const char name [], char firstname []) 49 {
50 int index ;
51 for (index = strlen (name); name[ index ] != ' '; index ) ; 52 int len = strlen (name) - index ;
53 strncpy (firstname , name + index + 1, len); 54 firstname [len] = '\0 ' ;
55 } 56
57 void Swap( Student &x, Student &y) 58 {
59 Student tmp;
60 tmp = x; x = y; y = tmp; 61 }
Hình 6.13: Hàm Sort_List() hàm phụ trợ
Hàm Count_List() cho thống kê đơn giản tính số lượng sinh viên nam số lượng sinh viên nữ
1 void Count_List () {
3 int num_male , num_female ; num_male = num_female = 0;
5 for (int index = 0; index < num_students ; index ++) if (List[ index ] sex == 0) num_male ++;
7 else num_female ++; cout << endl;
9 cout << " Number of Male is: " << num_male << endl; 10 cout << " Number of Female is: " << num_female << endl; 11 cout << " Total is: " << num_students << endl;
12 13 }
Hình 6.14: Hàm Count_List()
Và cuối cùng, để tránh tính nhàm chán phải tạo lại danh sách lần chạy chương trình, ta tự động nạp trước danh sách cố định cách gọi hàm Set_List() main() Điều khơng ảnh hưởng đến hoạt động chương trình cần ta tạo lại danh sách Make_List(), danh sách ghi đè lên thông tin cố định Set_List()
1 void Set_List () {
(142)dành
cho
hội
đồng
nghiệm
thu
134 Các kiểu liệu trừu tượng
8 }
10 void Make_List () 11 {
12 cout << " Enter the number of students : "; 13 cin >> num_students ;
14 for (int index = 0; index < num_students ; index ++) 15 {
16 cout << " Enter data values of student #" << index +1 << ":\n"; 17 List[ index ] = New_student () ;
18 } 19 }
Hình 6.15: Hàm Set_List()
Với thứ tự danh sách sinh viên nhập tự động (từ hàm Set_List()), ví dụ chọn chức xếp ta có kết bảng dưới:
Bảng 6.16: Sắp xếp sinh viên theo tên theo điểm
Thứ tự gốc Tăng dần theo tên Giảm dần theo điểm Tran Thanh Son 8.4 Le Nguyen Hanh 5.2 Cao Thuy Linh 9.5 Nguyen Thi Hanh 6.5 Nguyen Thi Hanh 6.5 Tran Thanh Son 8.4 Le Nguyen Hanh 5.2 Cao Thuy Linh 9.5 Nguyen Thi Hanh 6.5 Cao Thuy Linh 9.5 Tran Thanh Son 8.4 Le Nguyen Hanh 5.2 Mã nguồn chương trình hồn chỉnh cho phụ lục A.1
Trong thiết kế chương trình Quản lý sinh viên, ta cố gắng chia nhỏ chương trình nhỏ tốt theo triết lý thiết kế top-down chia-để-trị Cụ thể, đưa tồn mã hàm New_student() vào hàm Make_List(), nhiên lần ta lại phải đưa mã vào hàm Append_List() Insert_List() hàm gọi đến New_student() Đó lý để nhập thơng tin cho sinh viên xây dựng thành hàm riêng New_student() Tương tự, hàm Display_student() tách riêng gọi Display_List() Update_List() Và hàm Update_student() gọi Update_List() ta viết riêng thành hàm hoàn chỉnh, cách thiết kế có lợi cần thay đổi, mở rộng cấu trúc Student ta cần thay đổi, bổ sung hàm “con” độc lập hồn tồn với hàm gọi đến
6.2 Kiểu liệu trừu tượng lớp (class)
Phần trình bày lớp kiểu liệu mở rộng kiểu cấu trúc Lớp đóng vai trị trung tâm kỹ thuật lập trình hướng đối tượng, kỹ thuật lập trình phát triển so với kỹ thuật lập trình cấu trúc Những đặc trưng đặc sắc kĩ thuật dần trình bày đầy đủ chương sau giáo trình
(143)dành
cho
hội
đồng
nghiệm
thu
6.2 Kiểu liệu trừu tượng lớp (class) 135
ta viết hàm void Display(Date date) cần in thông tin sinh viên ta viết hàm void Display(Student student), chương trình có hai hàm chung mục đích, tên cách thức làm việc, khác chỗ hàm làm việc tập liệu riêng Rõ ràng, khơng thể dùng hàm Display(Date date) để in nội dung sinh viên ngược lại Cũng tương tự, liên quan đến Date có hàm Distance_Date() để tính khoảng cách ngày tháng, cịn Student khơng Tóm lại, tập hợp liệu có tập hàm xử lý riêng
Từ nhận xét trên, liệu theo hàm xử lý đó, “đóng” chung vào “gói” gói ta gọi lớp (class) Có nghĩa hàm Display(Date date) cấu trúc Date thành lớp hàm Display(Student student) cấu trúc Student thành lớp khác
Như vậy, lớp cấu trúc thành phần liệu (được gọi biến thành viên -member variables) thêm thành phần hàm xử lý liệu lớp Các hàm thành phần gọi hàm thành viên hay phương thức thành viên (member functions, member methods) Ý tưởng gắn chung loại thành phần vào kiểu liệu (cùng trở thành thành viên lớp) gọi đóng gói (encapsulation)
Từ thảo luận trên, ta xây dựng kiểu liệu với khai báo sau
6.2.1 Khai báo lớp class class_identifier {
member methods ; member variables ; };
Cũng giống cấu trúc, ta lưu ý (đừng quên) kết thúc khai báo dấu chấm phẩy Ngoài ra, trước dấu chấm phẩy ta khai báo kèm theo biến kể khởi tạo Trong lớp, thứ tự khai báo thành viên (variables methods) khơng quan trọng, nhiên có nhiều lý để ta ưa chuộng cách khai báo phương thức (methods) trước sau đến khai báo biến (variables)
1 # include <iostream > using namespace std;
4 class Date {
6 public:
7 void Display (); // method int day , month , year ; // variable };
10
11 class Student 12 {
13 public:
14 void Display (); // method 15 private:
(144)dành
cho
hội
đồng
nghiệm
thu
136 Các kiểu liệu trừu tượng
19 double mark; // variable
20 };
Hình 6.17: Ví dụ đơn giản khai báo lớp Date Student
Chú ý struct Date, hàm void Display(Date date) định nghĩa bên cấu trúc có đối kèm theo để biết hàm cần hiển thị liệu biến gọi Còn đây, class Date, hàm void Display() hàm khơng đối Vậy hàm gọi hiển thị liệu lấy từ đâu ? Điều giải thích dần sau Hiển nhiên, tất hàm lớp không đối
Trong khai báo trên, phương thức Display() chưa định nghĩa Định nghĩa phương thức đặt bên lớp lúc khai báo đặt bên ngồi lớp Vì lớp có nhiều phương thức nên việc đặt tất định nghĩa vào bên lớp làm cho mã lớp dài, khó theo dõi Do vậy, thơng thường phương thức khai báo bên định nghĩa chúng đặt bên
Khi định nghĩa phương thức bên ngồi lớp, để tránh nhầm lẫn (vì phương thức lớp khác trùng tên) ta cần định phương thức thuộc lớp cách tên lớp dấu :: trước tên phương thức Dấu :: (hai dấu hai chấm liền nhau) kí hiệu phép tốn định phạm vi (scope resolution operator) Ví dụ:
1 void Date :: Display () {
3 cout << day << "/" << month << "/" << year ; }
5
6 void Student :: Display () {
8 cout << name << "\t" ;
9 if (sex == 0) cout << "Male" << "\t" ; else cout << " Female " << "\t" ; 10 cout << mark << endl;
11 }
Hình 6.18: Định nghĩa phương thức Trong định nghĩa ta có vài điểm lưu ý:
• Hai hàm Display() khác nhau, lớp Date (với toán tử phạm vi Date ::) Student (Student ::)
• Các hàm khơng có đối, liệu sử dụng hàm lấy từ thành viên lớp
• Việc khai báo biến thành viên lớp khác (Date Student) phép (tức lớp có chứa lớp) nhiên, thời điểm ta chưa bàn đến cách sử dụng Do vậy, phương thức Student :: Display() tạm thời ta không in liệu biến thành viên birthday
6.2.2 Sử dụng lớp
Đối tượng
(145)dành
cho
hội
đồng
nghiệm
thu
6.2 Kiểu liệu trừu tượng lớp (class) 137
Date holiday , birthday ;
dùng để khai báo biến holiday birthday có kiểu lớp Date Trong lập trình hướng đối tượng, biến kiểu lớp gọi đối tượng (object) từ sau, ta dùng từ để nói biến kiểu lớp
Tương tự cấu trúc, để truy cập đến thành viên lớp ta sử dụng phép tốn chấm (dot operator) Ví dụ:
holiday day = ; holiday month = ; holiday year = 2015 ; holiday Display () ;
Ba dòng lệnh đầu dùng để gán giá trị ngày 1/5/2015 cho đối tượng holiday Dòng lệnh thứ hiểu holiday truy cập đến gọi đến phương thức Display(), phương thức Display() thực liệu liên quan đến biến phương thức lấy từ holiday Từ câu lệnh holiday.Display() in nội dung holiday, tức 1/5/2015 hình Dưới ví dụ tổng hợp ý
1 # include <iostream > using namespace std;
4 class Date {
6 public:
7 void Display (); // method int day , month , year ; // variable };
10
11 class Student 12 {
13 public:
14 void Display (); // method 15 private:
16 char name [30]; // variable 17 Date birthday ; // variable 18 int sex; // variable
19 double mark; // variable
20 }; 21
22 void Date :: Display () { } // code vi du truoc 23 void Student :: Display () { } // code vi du truoc 24
25 int main () 26 {
27 Date holiday , birthday ; 28 holiday day = ;
29 holiday month = ; 30 holiday year = 2015 ; 31 holiday Display () ; 32 cout << endl;
33
(146)dành
cho
hội
đồng
nghiệm
thu
138 Các kiểu liệu trừu tượng
Hình 6.19: Truy xuất thành viên lớp Tính đóng gói từ khóa public:, private:
Lớp kiểu liệu giống cấu trúc, nhiên chất khác xa so với cấu trúc Điểm giống hai loại, lưu trữ liệu Việc xử lý liệu hàm đảm nhiệm Các hàm xuất đâu, chương trình nào, xử lý loại liệu thành viên nhóm lập trình viết Với tính chất “rộng mở” vậy, độ an tồn, tính qn liệu khơng chắn ! Do đó, cần phân định rõ hàm phép làm việc với liệu Sau phân định xong, nên “đóng” kín tất thành viên liệu hàm vào “gói” cách ly với bên ngồi để bảo vệ Các gói ta gọi lớp Mỗi lớp đặc tả, trừu tượng hóa thực thể thực tế lớp ngày tháng, lớp nhà cửa, lớp xe cộ, lớp xâu kí tự … Các thành viên lớp mặc định cách ly với bên ngồi, có nghĩa hàm, câu lệnh nằm bên ngồi lớp khơng quyền truy xuất đến thành viên lớp (hiển nhiên, thành viên lớp quyền truy xuất lẫn nhau)
Tuy nhiên, việc giao tiếp với bên ngồi khơng thể tránh khỏi (vật chất luôn vận động), số thành viên lớp cần cấp phép cách khai báo từ khóa public: trước thành viên Thơng thường, liệu cần bảo vệ, nên không “cấp phép” ngoại trừ trường hợp đặc biệt, lại phương thức đại diện cho lớp để giao tiếp với bên nên thường khai báo dạng public Tuy mặc định thành viên lớp khép kín (riêng biệt) để rõ ràng, lúc cần ta đặt từ khóa private: trước thành viên không phép public để thành viên riêng, bên ngồi khơng quyền truy xuất, gọi đến
• Trạng thái public: Các thành viên khai báo trạng thái có nghĩa bên ngồi lớp sử dụng được, thường phương thức
• Trạng thái private: Các thành viên sử dụng thành viên khác bên lớp, thường biến
Các từ khóa trạng thái chung (public) riêng (private) khơng thiết phải đặt trước thành viên mà từ điểm xuất thành viên phía sau có đặc tính gặp từ khóa ngược lại Ví dụ lớp khai báo trên, ta có thành viên Date public, lớp Student có Display() thành viên public, lại (các biến liệu) thành viên private
Ta xem lại chương trình làm việc với lớp Date # include <iostream >
2 using namespace std;
4 class Date {
6 public:
7 void Display ();
8 int day , month , year ; };
10
(147)dành
cho
hội
đồng
nghiệm
thu
6.2 Kiểu liệu trừu tượng lớp (class) 139
12 {
13 cout << day << "/" << month << "/" << year ; 14 }
15
16 int main () 17 {
18 Date holiday , birthday ; 19 holiday day = ;
20 holiday month = ; 21 holiday year = 2015 ; 22 holiday Display () ; 23 cout << endl;
24
25 return 0 ; 26 }
Hình 6.20: Lớp Date
Trong chương trình, từ ngồi lớp Date (trong hàm main) ta có câu lệnh gọi thành viên Date:
holiday day = ; holiday month = ; holiday year = 2015 ; holiday Display () ;
Cả câu lệnh hợp lệ chương trình chạy tốt thành viên khai báo dạng public Tuy nhiên, với ý tưởng cần che giấu, bảo vệ liệu cách khai báo khơng tốt Có nghĩa ta cần khai báo lại biến private, lớp Date sau:
class Date {
public:
void Display ();
private:
int day , month , year ; };
Đây cách khai báo tốt, nhiên day, month, year private nên dòng lệnh gán chương trình gây lỗi Ba biến khơng phép truy cập từ bên ngồi (ở hàm main), truy cập phương thức bên Date Do vậy, để khắc phục, ta xây dựng phương thức thành viên public lớp Phương thức không gán giá trị cố định 1/5/2015 cho đối tượng mà tổng quát hơn, gán giá trị dd/mm/yy cho đối tượng, từ phương thức xây dựng có đối tương ứng
Ta xây dựng lại Date cách cho biến thành viên day, month, year private phương thức void Set(int dd, int mm, int yy); dùng để gán giá trị cho biến chương trình bên
1 # include <iostream > using namespace std;
4 class Date {
(148)dành
cho
hội
đồng
nghiệm
thu
140 Các kiểu liệu trừu tượng
7 void Display ();
8 void Set(int dd , int mm , int yy); private:
10 int day , month , year ; 11 };
12
13 void Date :: Display () 14 {
15 cout << day << "/" << month << "/" << year ; 16 }
17
18 void Date :: Set(int dd , int mm , int yy) 19 {
20 day = dd ; 21 month = mm ; 22 year = yy ; 23 }
24
25 int main () 26 {
27 Date holiday ;
28 holiday Set (1, 5, 2015) ; 29 holiday Display () ;
30 cout << endl; 31
32 return 0 ; 33 }
Hình 6.21: Lớp Date che giấu liệu
Cũng tương tự, giả sử ta cần in ngày, tháng ngày lễ, ta viết như: cout << holiday.day << ", " << holiday.month ; Rõ ràng, ta cần đưa thêm vào lớp phương thức (public) cho phép truy xuất, trả lại giá trị biến thành viên như:
int getDay () ;
int getMonth ();
int getYear ();
khi lớp Date mở rộng thành: # include <iostream >
2 using namespace std;
4 class Date {
6 public:
7 void Display ();
8 void Set(int dd , int mm , int yy); int getDay () { return day; } 10 int getMonth () { return month ; } 11 int getYear () { return year; } 12 private:
13 int day , month , year ; 14 };
(149)dành
cho
hội
đồng
nghiệm
thu
6.2 Kiểu liệu trừu tượng lớp (class) 141
16 void Date :: Display () { } // code vi du truoc 17 void Date :: Set(int dd , int mm , int yy) { } // code vi du truoc 18
19 int main () 20 {
21 Date holiday ;
22 holiday Set (1, 5, 2015) ;
23 cout << holiday getDay () << "/" << holiday getMonth () << " of every year is
holiday " ;
24 cout << endl; 25
26 return 0 ; 27 }
Hình 6.22: Lớp Date với hàm truy xuất liệu Tóm lại :
cout << holiday day ; // khơng dùng day private cout << holiday getDay () ; // dùng getDay() public
Thông thường định nghĩa hàm không dài, ta đặt khai báo lớp Các hàm get lớp ví dụ
Với chế đóng gói, liệu muốn giao tiếp với bên ngồi (cũng có nghĩa bên ngồi muốn truy xuất đến liệu lớp) phải thơng qua phương thức lớp (dĩ nhiên phải public), điều đảm bảo tính an toàn quán liệu, giúp người lập trình tránh sai sót mã chương trình Điều tương tự việc hàng xóm muốn nói chuyện với nhà bên cạnh thường phải thông qua Bố Mẹ để tiếp cận với người tiếng cần thông qua quản lý họ Những người tiếng giống trẻ em liệu thực thể cần bảo vệ
Ngoài từ khóa public, private thành viên lớp cịn khai báo với từ khóa protected Ảnh hưởng từ khóa giống private, nhiên cho phép mềm dẻo lớp thừa kế Phù hợp với mức độ, giáo trình khơng trình bày tính thừa kế, người đọc tìm hiểu thêm giáo trình C/C++
Tóm lại, chế đóng gói đảm bảo:
• Tính an tồn: Các thành viên private (thường biến chứa liệu) bất khả xâm phạm (không truy cập từ bên ngồi), truy cập thành viên (phương thức) lớp
• Tính qn: Dữ liệu lớp xử lý phương thức lớp đó, cách làm việc qui định trước lớp
Cơ chế đóng gói cịn có đặc trưng quan trọng cho phép ta tạo lớp liệu hoàn chỉnh, khép kín, độc lập với chương trình Điều có nghĩa ta thiết kế lớp đủ tổng quát cài đặt hồn chỉnh ta sử dụng lại lớp chương trình khác mà khơng cần phải sửa mã cho phù hợp với chương trình
(150)dành
cho
hội
đồng
nghiệm
thu
142 Các kiểu liệu trừu tượng
Lớp Hàm
private:
Hàm
public:
Hàm
private: public:
Lớp Date Hàm
display() getDay()
day, month, year
Hình 6.23: Minh họa chế đóng gói Phép gán
Cũng giống cấu trúc, hai đối tượng lớp phép gán giá trị cho Ví dụ:
Date holiday , birthday ; holiday Set (8, 3, 2015) ; birthday = holiday ;
birthday Display (); // 8/3/2015
Và giống cấu trúc, gán cho hai đối tượng so sánh (==) với nhau, so sánh biến thành viên Tuy nhiên, phần sau học cách làm để viết phép toán cho lớp (ví dụ phép tốn ==)
Tóm tắt số đặc trưng lớp
Ngoại trừ tính chất cấu trúc, lớp cịn có tính chất liên quan đến phương thức tóm tắt đây:
• Lớp gồm loại thành viên: biến phương thức (hoặc hàm)
• Định nghĩa phương thức đặt bên ngồi lớp
• Mỗi thành viên hai trạng thái : public (chung) private (riêng) • Các thành viên lớp truy cập lẫn (Phương thức gọi đến
(151)dành
cho
hội
đồng
nghiệm
thu
6.2 Kiểu liệu trừu tượng lớp (class) 143
• Bên ngồi truy cập đến thành viên public lớp, truy cập đến thành viên private (Các thành viên private truy cập phương thức lớp)
• Thơng thường biến thành viên private, phương thức public
• Các phương thức thành viên phép khai báo định nghĩa giống hàm thông thường (đối mặc định, chồng (trùng tên), … )
• Một lớp sử dụng lớp khác để làm biến thành viên
• Đối giá trị trả lại phương thức phép đối tượng lớp
• Để chạy phương thức cần có đối tượng gọi đến phương thức Ví dụ: holiday.Display() • Khi chạy phương thức, biến thành viên viết phương thức nhận liệu
truyền liệu đối tượng gọi đến phương thức
6.2.3 Bài tốn Quản lý sinh viên
Trong phần này, ta tiếp tục minh họa cách viết sử dụng lớp thơng qua tốn QLSV mô tả mục 6.1.3 (tạm gọi phiên A1) Với phiên A2, thực thể ngày tháng sinh viên tổ chức thành lớp, thay đổi hai phiên trình bày phần Chương trình hồn chỉnh cuối trình bày Phụ lục A2
Chức chương trình - hàm main()
Hồn tồn giống với A1, hàm main() không thay đổi, chủ yếu tạo menu cho người dùng lựa chọn chức cần thực (xem 6.1.3)
Khai báo lớp
1 // - Khai bao lop thang class Date
3 {
4 public:
5 void Display ();
6 void Set(int dd , int mm , int yy);
7 int getDay () { return day; } // tra lai ngay int getMonth () { return month ; } // tra lai thang int getYear () { return year; } // tra lai nam 10 private:
11 int day , month , year ; 12 };
13
14 // - Khai bao lop sinh vien 15 class Student
16 {
17 public:
18 void Display ();
19 void Set(char nme [], int dd , int mm , int yy , int sx , double mrk); 20 void New ();
21 void Update ();
(152)dành
cho
hội
đồng
nghiệm
thu
144 Các kiểu liệu trừu tượng
23 Date getBirthday () { return birthday ; } // tra lai sinh 24 int getSex () { return sex; } // tra lai gioi tinh 25 double getMark () { return mark; } // tra lai diem 26 private:
27 char name [30]; 28 Date birthday ; 29 int sex;
30 double mark; 31 };
Hình 6.24: Khai báo lớp chương trình QLSV
Hai cấu trúc Date Student chuyển thành lớp Các thành phần cấu trúc cũ biến thành viên đặt từ khóa private Ta bổ sung vào lớp phương thức sửa chữa từ hàm A1 phương thức theo nhu cầu Một số phương thức thành viên định nghĩa phần thân khai báo lớp (thường phương thức có định nghĩa khoảng vài dịng lệnh) Các phương thức cịn lại định nghĩa bên ngồi khai báo trình bày phần Student có biến thành viên lớp Date (lớp lớp)
Về phương thức, ta phân bố lại hàm displayDate() displayStudent() A1 cho lớp lấy tên Display() Các vị ngữ Date hay Student khơng cần thiết tính đóng gói hàm Display() độc lập
Một phát sinh so với A1 hàm bên ngồi khơng cịn truy cập trực tiếp vào biến thành viên lớp, nhiên, có hai nhóm thao tác ta cần dùng liên quan đến truy cập biến thành viên đặt lại giá trị lấy giá trị biến Do vậy, lớp ta cần cài đặt thêm nhóm phương thức qui ước set (đặt lại giá trị) get (lấy giá trị) Định nghĩa phương thức
1 // - Hien thi thang void Date :: Display ()
3 {
4 cout << day << "/" << month << "/" << year ; }
6
7 // - Dat gia tri cho thang void Date :: Set(int dd , int mm , int yy)
9 {
10 day = dd ; 11 month = mm ; 12 year = yy ; 13 }
14
15 // - Hien thi thong tin sinh vien 16 void Student :: Display ()
17 {
18 cout << name << "\t" ;
19 cout << setw (2) << birthday getDay () << "/" << setw (2) << birthday getMonth () <<
"/" << setw (2) << birthday getYear () << "\t" ;
20 if (sex == 0) cout << "Male" << "\t" ; else cout << " Female " << "\t" ; 21 cout << mark << endl;
22 } 23
(153)dành
cho
hội
đồng
nghiệm
thu
6.2 Kiểu liệu trừu tượng lớp (class) 145
25 void Student :: Set(char nme [], int dd , int mm , int yy , int sx , double mrk) 26 {
27 strcpy (name , nme) ;
28 birthday Set(dd , mm , yy); // goi ham Set cua Date 29 sex = sx;
30 mark = mrk; 31 }
32
33 // - Tao moi mot sinh vien tu ban phim 34 void Student :: New ()
35 {
36 int mm , dd , yy ; 37 fflush ( stdin ) ;
38 cout << " Full name: "; cin getline (name , 30) ;
39 cout << " Birthday : "; cin >> dd >> mm >> yy ; birthday Set(dd , mm , yy) ; 40 cout << " Sex (0: Male , 1: Female ): "; cin >> (sex) ;
41 cout << " Mark: "; cin >> (mark) ; 42 }
43
44 // - Cap nhat thong tin mot sinh vien 45 void Student :: Update ()
46 {
47 Date brday ; // su dung lop khac phuong thuc 48 char nme [30]; int dd , mm , yy; int sx; double mrk; // cac gia tri moi 49 // Lay noi dung cua doi tuong cac bien le ben ngoai
50 getName (nme); brday = getBirthday (); sx = getSex (); mrk = getMark (); 51 dd = brday getDay () ; mm = brday getMonth () ; yy = brday getYear () ; 52 Display (); // hien noi dung ban ghi can sua 53
54 int choice ; 55 do {
56 cout << endl;
57 cout << "1: Name" << endl ; 58 cout << "2: Birthday " << endl ; 59 cout << "3: Sex" << endl ; 60 cout << "4: Mark" << endl ; 61 cout << "0: Exit" << endl ;
62 cout << " Select member to update ? " ; 63 cin >> choice ; cin ignore ();
64 switch ( choice )
65 {
66 case 1: cout << "Enter new name: " ; cin getline (nme , 30) ; break; 67 case 2: cout << "Enter new birthday : " ; cin >> dd >> mm >> yy ; break; 68 case 3: cout << "Enter new sex: " ; cin >> sx ; break;
69 case 4: cout << "Enter new mark: " ; cin >> mrk ; break;
70 }
71 } while ( choice ) ; 72 int sure ;
73 cout << "Are you sure for updating (1: sure , 0: not sure) ? " ; cin >> sure ; 74 if (sure)
75 {
76 Set(nme , dd , mm , yy , sx , mrk) ;
(154)dành
cho
hội
đồng
nghiệm
thu
146 Các kiểu liệu trừu tượng
79 else cout << "\ nUpdating cancelled \n"; 80
81 }
Hình 6.25: Các phương thức Date Student
Các phương thức khai báo chưa cài đặt (bên phần khai báo) cài đặt ln có tốn tử định phạm vi :: kèm tên lớp, trùng tên phương thức không thành vấn đề Hầu hết phương thức chuyển “ngang” từ A1 sang này, trừ vài ý phải sửa chữa Ví dụ phương thức Student::Display() ta cần hiển thị ngày sinh sinh viên, ta viết cout << birthday.day A1 thành viên private lớp Date Student::Display() khơng có quyền gọi đến Để hiển thị day ta cần phải thông qua thành viên public lớp Date getDay(), từ câu lệnh phải sửa thành: cout << birthday.getDay() Tương tự để đặt lại giá trị ngày tháng năm sinh sinh viên (hàm Student::Set()) ta phải cầu viện đến thành viên Date Date::Set(), có nghĩa khơng thể viết birthday.day = dd … mà phải cụm: birthday.Set(dd, mm, yy); (lưu ý birthday đối tượng Date)
Hàm Student::New() cho phép tạo đối tượng với liệu nhập từ bàn phím (cũng dạng Set) Trừ biến thành viên họ tên, giới tính, điểm mà New quyền truy cập, biến thuộc lớp khác (birthday thuộc Date) ta phải nhập qua biến thường trung gian (dd, mm, yy) sau gọi đến hàm Set Date để gán giá trị cho birthday
Cách làm việc hàm Student::Update() giống A1 Để đảm bảo an toàn ta cập nhật sinh viên cần sửa Từ đó, ta cần đến nhóm hàm get Student để lấy thông tin sinh viên biến lẻ bên tiến hành sửa chữa giá trị biến Chỉ sau NSD đồng ý cập nhật ta dùng đến hàm Student::Set() để đặt lại giá trị sinh viên vừa sửa chữa
Các biến hàm lại chương trình
Các biến danh sách sinh viên, số sinh viên khai báo A1 Các hàm phục vụ như: getFirstname, swap giống hoàn toàn A1 Trong swap, đối tượng (kiểu lớp) truyền theo tham chiếu cấu trúc
Các hàm chức chương trình, cần sửa chữa nhỏ, chủ yếu tập trung vào thay đổi từ kiểu cấu trúc sang lớp Ví dụ để tạo sinh viên gán vào ô index List, A1 ta gọi chức dòng lệnh: List[index] = New_student() ; cịn thay dịng lệnh: List[index].New() ;
Chương trình hồn chỉnh phiên này, người đọc tham khảo Phụ lục A2
6.2.4 Khởi tạo (giá trị ban đầu) cho đối tượng
Hàm tạo (Constructor function)
(155)dành
cho
hội
đồng
nghiệm
thu
6.2 Kiểu liệu trừu tượng lớp (class) 147
Như vậy, hàm tạo phương thức lớp (nhưng phương thức đặc biệt) dùng để tạo dựng đối tượng gọi chạy tự động Khi gặp khai báo, chương trình dịch cấp phát nhớ cho đối tượng sau gọi đến hàm tạo
Tuy nhiên, lớp ta không viết hàm tạo chương trình cung cấp hàm tạo gọi hàm tạo mặc định Mỗi có đối tượng khai báo, hàm tạo gọi, đơn giản khơng làm Các giá trị đối tượng ngẫu nhiên (ta hay gọi “rác”) Như vậy, mục đích hàm tạo mặc định đơn giản để phù hợp với cách hoạt động C++
Nói chung hầu hết lớp cần có hàm tạo NSD viết để phục vụ cho việc gán giá trị ban đầu cho lớp
• Hàm tạo khơng đối Ví dụ sau mơ tả hàm tạo không đối cho lớp Date: Date :: Date ()
2 {
3 day = ; month = ; year = 2000 ; }
Hình 6.26: Hàm tạo Date()
Hàm tạo gán giá trị ngày tháng năm 2000 cho đối tượng khai báo
• Hàm tạo có đối Vẫn lớp Date ta viết thêm hàm tạo có đối sau: Date :: Date(int dd , int mm , int yy)
2 {
3 day = dd ; month = mm ; year = yy ; }
Hình 6.27: Hàm tạo có đối số
Như vậy, lớp khơng có hàm tạo (sử dụng hàm tạo mặc định, giá trị biến lúc “rác”), có hàm tạo có nhiều hàm tạo, có đối khơng có đối Nói chung, để đối tượng khởi tạo cách tường minh, hầu hết lớp có hàm tạo khơng đối
Trường hợp lớp có nhiều hàm tạo, hàm gọi đối tượng khai báo ? Để lựa chọn, chương trình so sánh số lượng giá trị kèm theo đối tượng khai báo với số đối hàm để định chạy hàm
Ví dụ quay lại hai hàm tạo trên, khai báo Date holiday; (không giá trị kèm theo) gọi chạy hàm thứ (không đối), holiday = 1/1/2000 Nếu khai báo Date holiday(20, 11, 2015) hàm khởi tạo thứ hai (có đối) gọi, tham đối dd, mm, yy nhận giá trị 20, 11, 2015 gán cho biến thành viên day, month, year, holiday có giá trị ban đầu 20/11/2015
(156)dành
cho
hội
đồng
nghiệm
thu
148 Các kiểu liệu trừu tượng
Date :: Date(int dd , int mm , int yy = 2000)
Khi đó, khai báo Date holiday(20, 11) gọi hàm tạo thứ giá trị holiday 20/11/2000
Lưu ý hàm tạo có dạng chung mục đích hàm đặt tên Set ví dụ trước Tuy nhiên, hàm tạo gọi chạy tự động lần sau lúc đối tượng khai báo, để khởi tạo (initialize) giá trị ban đầu cho đối tượng, cịn hàm Set chạy lần có đối tượng gọi đến để “setup” lại giá trị đối tượng
• Tính nhập nhằng có hàm tạo có đối khơng có hàm tạo khơng đối.
Khi lớp khơng có hàm tạo chương trình dịch cung cấp hàm tạo ngầm định hàm khơng làm Khi lớp có hàm tạo chương trình dịch không phát sinh hàm tạo ngầm định Do đó, lớp có hàm tạo có đối khơng có hàm tạo khơng đối dễ gây lỗi Cụ thể, trường hợp ta khai báo đối tượng không giá trị kèm theo chương trình khơng thể khởi tạo cho đối tượng (vì ta khơng xây dựng hàm tạo khơng đối chương trình khơng phát sinh hàm tạo ngầm định) Do vậy, cách viết chương trình tốt lớp nên ln ln có hàm tạo khơng đối, hàm tạo có tất đối mặc định, hàm phục vụ hàm tạo khơng đối
• Cú pháp số đặc điểm hàm tạo.
– Tên hàm tạo: Tên hàm tạo bắt buộc phải trùng với tên lớp (ví dụ Date()). – Hàm tạo khơng có kết trả về.
– Khơng khai báo kiểu cho hàm tạo (kể void).
– Hàm tạo xây dựng bên bên ngồi định nghĩa lớp.
– Hàm tạo có đối khơng đối, danh sách đối có đối mặc định
– Trong lớp có nhiều hàm tạo (cùng tên khác số đối). • Hàm tạo với phần khởi tạo trước.
Ngồi hàm tạo mặc định, khơng đối, có đối ta cịn dạng hàm tạo với phần khởi tạo trước Để đơn giản, ta minh họa ví dụ hàm tạo dạng này:
Date :: Date () : day (1), month (1), year (2015) // hàm khơng đối { // khơng có dịng lệnh nào
}
Hàm tạo tương đương với hàm tạo:
Date () {
(157)dành
cho
hội
đồng
nghiệm
thu
6.2 Kiểu liệu trừu tượng lớp (class) 149
Hoặc xét hàm tạo:
// hàm có hai đối
Date :: Date(int dd , int mm) : yy (2015) {
day = dd; month = mm; }
Hàm tạo tương đương với
Date(int dd , int mm) {
day = dd; month = mm; year = 2015; }
Khai báo Date holiday(8, 3); tạo đối tượng holiday với giá trị 8/3/2015
Như vậy, hàm tạo với phần khởi tạo (ngay dòng tiêu đề hàm) thiết lập giá trị cụ thể (có thể biểu thức) cho nhiều thuộc tính đối tượng Các thuộc tính cịn lại thiết lập giá trị câu lệnh bên hàm tạo Thực tế, hàm tạo với phần khởi tạo trước thay cách tương ứng hàm tạo có đối thơng thường
Danh sách thuộc tính cần khởi tạo trước viết nối tiêu đề hàm với ngăn cách dấu hai chấm (':') Mỗi thuộc tính cần khởi tạo gồm tên biến thuộc tính giá trị (có thể biểu thức) nằm cặp dấu ngoặc tròn Các thuộc tính cần khởi tạo trước cách dấu phẩy (',')
• Gọi hàm tạo phương thức thông thường.
Như trên, hàm tạo gọi cách tự động có đối tượng khai báo Tuy nhiên, ta gọi hàm tạo cách tường minh câu lệnh:
Date (1, 1, 2015); // Date tên lớp Khơng có tên đối tượng
Câu lệnh tạo đối tượng vô danh (khơng có tên) sau gọi đến hàm tạo với đối (giả sử cài đặt ví dụ trước) để gán giá trị cho đối tượng Như vậy, đối tượng vô danh có giá trị 1/1/2015 Mục đích sử dụng đối tượng vơ danh (trong số giáo trình cịn gọi đối tượng hằng) để gán giá trị cho đối tượng khác Ví dụ:
Date holiday ; // gọi hàm khởi tạo không đối.
// holiday gán giá trị "mới" 2/9/1945 holiday = Date (2, 9, 1945) ;
Như vậy, cách gán giá trị dạng tương tự hàm Set phần
• Bảng tóm tắt dạng khởi tạo hàm tạo Phần ta giả thiết loại dạng hàm tạo đã đề cập bên có cài đặt lớp Date Giả sử ta cần tạo đối tượng holiday gán giá trị 8/3/2015 câu lệnh sau:
(158)dành
cho
hội
đồng
nghiệm
thu
150 Các kiểu liệu trừu tượng
gọi hàm tạo không đối, giả sử hàm viết để khởi tạo cho đối tượng với giá trị khơng đổi 8/3/2015 Nếu lớp khơng có hàm tạo câu lệnh cho đối tượng holiday với giá trị chưa xác định Nếu lớp có hàm tạo khơng có hàm tạo khơng đối câu lệnh bị chương trình dịch báo lỗi (nói chung, nên có hàm tạo không đối)
Date holiday (8, 3, 2015); // gọi hàm tạo đối
// gọi hàm tạo đối, có đối mặc định year = 2015. Date holiday (8, 3);
Cũng gọi hàm tạo hai đối có câu lệnh gán year = 2015 Hoặc gọi hàm tạo hai phần khởi tạo trước year = 2015 Trường hợp lớp có lúc hai ba hàm tạo dạng chương trình dịch báo lỗi tính nhập nhằng (chỉ nên viết một)
Date holiday ;
// gán giá trị đối tượng hằng, hoặc holiday = Date (8, 3, 2015);
// gán giá trị phương thức Set (đã viết) holiday Set (8, 3, 2015);
Hàm tạo chép (Copy constructor)
Ngoài việc khởi tạo đối tượng từ giá trị cụ thể (được hỗ trợ hàm tạo), ta khởi tạo đối tượng từ giá trị đối tượng khác hàm tạo chép Ví dụ xét khai báo khởi tạo cho đối tượng holiday birthday:
Date holiday (1, 5, 2015); // từ giá trị cụ thể Date birthday ( holiday ) ; // từ đối tượng khác
Nếu lớp chưa có hàm tạo chép, câu lệnh gọi tới hàm tạo chép mặc định Hàm chép nội dung bit holiday vào bit tương ứng birthday Trong đa số trường hợp, lớp khơng có thuộc tính kiểu trỏ hay tham chiếu, việc dùng hàm tạo chép mặc định (để tạo đối tượng có nội dung đối tượng cho trước) đủ không cần xây dựng hàm tạo chép Ví dụ trường hợp ta có birthday = 1/5/2015
Trong trường hợp khác ta xây dựng hàm tạo chép theo mẫu:
class_name (const class_name & object ) {
}
Trong đó, đối tượng tham đối object khai báo dạng tham chiếu để tiết kiệm nhớ thời gian truyền, ta thêm từ khóa const khơng muốn giá trị object bị thay đổi sau hàm gọi thực
Trong mẫu khai báo trên, ta lưu ý hàm tạo chép khơng có kiểu đối trả lại thân hàm khơng có câu lệnh trả lại giá trị
Ví dụ xây dựng hàm tạo chép cho lớp Date sau: # include <iostream >
(159)dành
cho
hội
đồng
nghiệm
thu
6.2 Kiểu liệu trừu tượng lớp (class) 151
5 class Date {
7 public: Date ();
9 Date(int dd , int mm , int yy); 10 Date(const Date & source ); 11 void Display ();
12 // 13 private:
14 int day , month , year ; 15 };
16
17 void Date :: Display () 18 {
19 cout << day << "/" << month << "/" << year ; 20 }
21
22 Date :: Date(int dd , int mm , int yy) 23 {
24 day = dd ; 25 month = mm ; 26 year = yy ; 27 }
28
29 Date :: Date(const Date & source ) 30 {
31 day = source day ; 32 month = source month ; 33 year = source year ;
34 cout << "The new object is initialized with values " << day << "/" << month << "/
" << year << endl;
35 } 36
37 int main () 38 {
39
40 Date d(2 ,3 ,4); 41 Date t(d); 42 t Display (); 43 cout << endl; 44
45 return 0; 46 }
Hình 6.28: Hàm tạo chép cho lớp Date
(160)dành
cho
hội
đồng
nghiệm
thu
152 Các kiểu liệu trừu tượng
6.2.5 Hủy đối tượng
Cũng giống biến đơn giản khác, biến khai báo mơđun (chương trình, khối lệnh, hàm …) sau chạy xong mơđun biến tự hủy, đối tượng tương tự Tuy nhiên, đối tượng trình khởi tạo hoạt động sinh số vấn đề khác xin cấp phát nhớ, nhớ không tự hủy …, cần viết phương thức đặc biệt cách tường minh để giải vấn đề Phương thức đặc biệt gọi hàm huỷ Hàm hủy (Deconstruction function)
Hàm hủy hàm thành viên lớp (phương thức) có chức ngược với hàm tạo Hàm hủy chương trình gọi tự động trước đối tượng tự hủy để thực số công việc có tính “dọn dẹp” liên quan đến đối tượng
• Hàm hủy mặc định Nếu lớp khơng định nghĩa hàm hủy, hàm hủy mặc định khơng làm phát sinh Đối với nhiều lớp hàm hủy mặc định đủ, khơng cần đưa vào hàm hủy Thông thường, hàm hủy cần phải giải phóng nhớ trình hoạt động đối tượng yêu cầu cấp phát
• Quy tắc viết hàm hủy.
– Tên hàm hủy gồm dấu ngã (đứng trước) tên lớp:
~ class_name ()
– Hàm hủy khơng có đối.
– Là hàm khơng có kiểu, khơng có giá trị trả về.
– Mỗi lớp có hàm hủy, phương thức lớp, định nghĩa trong ngồi khai báo lớp Ví dụ: D̃ate(); hàm hủy cho lớp Date
6.2.6 Hàm bạn (friend function)
Ý nghĩa hàm bạn
Như biết, thuộc tính private lớp đóng kín bên ngồi, để truy cập chúng cần phải thơng qua nhóm hàm get (để lấy giá trị) Tuy nhiên, với lớp có nhiều thuộc tính việc viết hàm get dài Một kỹ thuật cho phép hàm truy cập vào thuộc tính khai báo hàm bạn lớp
Cách khai báo hàm bạn
Để hàm trở thành bạn lớp, ta cần khai báo tên hàm vào bên định nghĩa lớp kèm theo từ khóa friend đứng trước Hàm định nghĩa bên hàm thơng thường khác (khơng có tốn tử định phạm vi ::) Lời gọi hàm bạn giống lời gọi hàm thơng thường
Ví dụ: Ta xây dựng lớp số phức (Complex) gồm thuộc tính phần thực (real), phần ảo (image) hàm cộng (Plus) sau :
class Complex {
public:
(161)dành
cho
hội
đồng
nghiệm
thu
6.2 Kiểu liệu trừu tượng lớp (class) 153
{
Complex res;
res.real = real + x.real ; res image = image + x image ;
return res; }
private:
double real; // phần thực
double image ; // phần ảo };
Hàm Plus có đối kiểu số phức giá trị trả lại số phức Khi số phức x gọi đến hàm với đối y, hàm thực cộng x vào với y trả lại số phức kết Vì ta đặt z tổng x y câu lệnh:
Complex x, y, z;
// Gán giá trị cho x y z = x.Plus(y);
Cách viết không giống với cách thơng thường, ví dụ ta muốn viết z = Plus(x, y), nhiên hàm có hai đối nên khơng thể thành viên lớp Vì vậy, ta viết lại hàm Plus hàm thông thường bên lớp Complex sau:
Complex Plus( Complex a , Complex b) {
Complex res:
res.real = a.real + b.real ; res.image = a.image + b.image ;
return res; }
Tuy nhiên cách viết bị lỗi hàm Plus (khơng phải thành viên lớp) không phép truy cập đến thuộc tính private (real, image) lớp Để hàm hoạt động đúng, ta giải cách thơng qua hàm getReal, getImage để lấy giá trị Hoặc đơn giản hơn, ta cần đăng ký hàm bạn Complex cách khai báo thêm tên hàm vào bên lớp Complex từ khóa friend, chương trình hồn chỉnh bên
1 # include <iostream > using namespace std;
4 class Complex {
6 public:
7 Complex (); // ham tao khong doi
8 Complex (double r, double i); // ham tao co doi friend Complex Plus( Complex a, Complex b);
10 void Display (); 11 private:
12 double real; // phan thuc
13 double image ; // phan ao
14 }; 15
16 Complex :: Complex () // ham vien cua lop 17 {
(162)dành
cho
hội
đồng
nghiệm
thu
154 Các kiểu liệu trừu tượng
19 image = 0; 20 }
21
22 Complex :: Complex (double r, double i) // ham vien cua lop 23 {
24 real = r; 25 image = i; 26 }
27
28 void Complex :: Display () // ham vien cua lop 29 {
30 cout << real << " + " << image << "i"; 31 }
32
33 Complex Plus( Complex a, Complex b) // ham ngoai lop 34 {
35 Complex c;
36 c.real = a.real + b.real ; 37 c image = a.image + b image ; 38 return c;
39 } 40
41 int main () 42 {
43 Complex comp1 (1, 2); // + 2i 44 Complex comp2 (2, 3); // + 3i 45 Complex comp3 ;
46 comp3 = Plus(comp1 , comp2 ); 47 comp3 Display ();
48 cout << endl; 49
50 return 0; 51 }
Hình 6.29: Hàm bạn lớp Complex Hàm bạn nhiều lớp
Trong nhiều chương trình, hai lớp cần truy cập lẫn nhau, tương tác với khía cạnh Tuy nhiên, phương thức lớp lại khơng thể truy cập đến thuộc tính lớp ngược lại Khi đó, cách giải tốt ta viết hàm bạn hai lớp để truy nhập đến thuộc tính hai
Ví dụ nhân hai ma trận ta viết hàm nhân thành viên lớp Matrix để làm điều Tuy nhiên, để nhân ma trận (của lớp Matrix) với vectơ (của lớp Vector) hàm thành viên Matrix thực (vì khơng truy cập đến Vector), tương tự hàm thành viên Vector Do vậy, ta viết hàm bạn chung hai lớp
(163)dành
cho
hội
đồng
nghiệm
thu
6.2 Kiểu liệu trừu tượng lớp (class) 155
1 # include <iostream > using namespace std; class Vector ;
4 class Matrix ;
6 class Vector {
8 public:
9 void FillData (); 10 void Display ();
11 friend Vector Product (const Matrix &M, const Vector &v) ; 12 private:
13 int numElement ; // so phan tu cua vecto 14 double data [20]; // cac phan cua vecto 15 };
16
17 class Matrix 18 {
19 public:
20 void FillData ();
21 friend Vector Product (const Matrix &M, const Vector &v); 22 private:
23 int numRow , numCol ; // so dong , so cot
24 double data [20][20]; // cac phan tu cua ma tran 25 };
26
27 void Vector :: FillData () 28 {
29 cout << "\ nEnter number of vector elements : "; cin >> numElement ; 30 cout << " Enter elements of vector :\n";
31 for (int index = 1; index <= numElement ; ++ index ) 32 cin >> data[ index ];
33 } 34
35 void Matrix :: FillData () 36 {
37 cout << "\ nEnter number of rows and columns of matrix : "; cin >> numRow >> numCol
;
38 cout << " Enter elements of matrix :\n";
39 for (int index1 = 1; index1 <= numRow ; ++ index1 ) 40 for (int index2 = 1; index2 <= numCol ; ++ index2 ) 41 cin >> data[ index1 ][ index2 ];
42 } 43
44 void Vector :: Display () 45 {
46 cout << "\ nElements of vector is:\n";
47 for (int index = 1; index <= numElement ; ++ index ) 48 cout << data[ index ] << " ";
49 cout << endl; 50 }
51
(164)dành
cho
hội
đồng
nghiệm
thu
156 Các kiểu liệu trừu tượng
54 Vector res;
55 res numElement = M numRow ; // so ptu cua vecto tich la so dong cua M 56 for (int index = 1; index <= M numRow ; ++ index )
57 {
58 res.data[ index ] = 0;
59 for (int k = 1; k <= M numCol ; ++k)
60 res.data[ index ] += M.data[ index ][k] * v.data[k]; 61 }
62 return res; 63 }
64
65 int main () 66 {
67 Matrix Mat; 68 Vector vec;
69 Vector result ; // result = M * v 70 Mat FillData ();
71 vec FillData ();
72 result = Product (Mat , vec); 73 result Display ();
74
75 return 0; 76 }
Hình 6.30: Hàm bạn chung Vector Matrix
Vì kiểu Vector Matrix xuất định nghĩa (trong dòng tiêu đề hàm Product) hai lớp nên ta cần khai báo tên hai lớp trước định nghĩa chúng Để ngắn gọn, cài đặt lớp ta tạm lược bớt thành viên chưa cần thiết hàm tạo, hàm in ma trận …
6.2.7 Tạo phép toán cho lớp (hay tạo chồng phép toán - Operator
Overloading)
Chúng ta xem lại ví dụ hàm cộng số phức mục 6.2.6 Hàm định nghĩa hàm thành viên với lời gọi z = x.Plus(y) trực quan, quen thuộc với lời gọi z = Plus(x, y) Plus định nghĩa dạng hàm bên ngồi bạn lớp Thậm chí gần gũi ta viết z = x + y
Vấn đề giải cách đơn giản cách cần thay đổi tên hàm bạn (Plus) tên operator+ Nói cách khác, kí hiệu phép tốn (+, -, *, /, %, =, ==, >, >=, <<, >> … ) sử dụng kèm từ khóa operator để định nghĩa thành phép toán lớp (thực tế ta thấy kí hiệu phép tốn dùng với kiểu liệu khác nhau, phép – theo nghĩa hiệu hai số đảo dấu số, phép >> toán tử nhập liệu đẩy bit sang phải … ) Nội dung phép toán định nghĩa câu lệnh hàm định (ví dụ dùng dấu + để tính “hiệu” hai đối tượng !!!), nhiên thông thường ta nên cài đặt nội dung phù hợp với ý nghĩa kí hiệu quen dùng để phù hợp với qui định C++ tính ưu tiên phép toán chẳng hạn
(165)dành
cho
hội
đồng
nghiệm
thu
6.2 Kiểu liệu trừu tượng lớp (class) 157
phép -) thứ tự đối quan trọng
Dưới minh họa việc xây dựng lớp ngày tháng với tương đối đầy đủ số phép toán quen dùng
1 # include <iostream > using namespace std;
4 // number of days of months
5 const int NUM_DAYS [13] = {0 ,31 ,28 ,31 ,30 ,31 ,30 ,31 ,31 ,30 ,31 ,30 ,31}; class Date
7 {
8 public:
9 Date () {day = month = year = 1; }
10 Date(int new_day , int new_month , int new_year ) {day = new_day ; month = new_month ;
year = new_year ; }
11 friend long DtoN(Date date); 12 friend Date NtoD(long num_days );
13 friend void DoW(Date date , char dow []); 14 friend void Moon_Year (Date date , char mc []); 15 friend long operator-( Date date1 , Date date2 ); 16 friend Date operator-( Date date , long n); 17 friend Date operator+( Date date , long n);
18 friend bool operator==( Date date1 , Date date2 ); 19 friend istream & operator>>( istream & is , Date& date); 20 friend ostream & operator<<( ostream & os , Date date); 21 friend Date operator++( Date& date);
22 friend Date operator ( Date& date);
23 friend Date operator++( Date& date , int n); 24 friend Date operator ( Date& date , int n); 25 private:
26 int day , month , year ; 27 };
Hình 6.31: Khai báo tốn tử cho lớp Date
Trong khai báo so với Date viết trước đây, ta lược bớt hàm Set dùng để thiết lập giá trị cho Date thay hàm khởi tạo có đối Date(int, int, int) Tương tự hàm Display() thay phép toán hiển thị << hàm Distance_Date(Date date1, Date date2) thay phép toán trừ
Để thuận lợi cài đặt, tất hàm phép tốn cịn lại khai báo hàm bạn Date
Các hàm bissextile_year (kiểm tra năm nhuận) num_days_of_month (tính số ngày tháng) khai báo độc lập bên ngồi lớp hàm phục vụ thơng thường, thành viên lớp
Các hàm DtoN, NtoD, DoW, Moon_Year cài đặt phiên cấu trúc đề cập mục kiểu liệu cấu trúc Thuật toán cho hàm Moon_Year() (đổi năm dương lịch sang năm âm lịch) tương tự thuật tốn tìm thứ ngày tháng (DoW)
Các phép tốn cịn lại gồm có:
(166)dành
cho
hội
đồng
nghiệm
thu
158 Các kiểu liệu trừu tượng
• Phép trừ date với số nguyên cho lại date
• Phép cộng date với số nguyên cho lại date Thực chất hai phép toán cộng, trừ với số nguyên cần cài đặt (khi gọi số nguyên âm dương)
• Phép tốn so sánh trả lại giá trị thành phần date sai ngược lại
• Cần lưu ý phép toán vào/ra
1 istream & operator>>( istream & is , Date& date) // Phep toan nhap {
3 int dd , mm , yy;
4 cin >> dd >> mm >> yy; date = Date(dd , mm , yy); return is;
7 }
9 ostream & operator<<( ostream & os , Date date) // Phep toan hien thi 10 {
11 os << date.day << "/" << date month << "/" << date.year ; 12 return os;
13 }
Hình 6.32: Nhập - xuất cho lớp Date
Các hàm có đối: Một đối để dòng nhập/xuất thuộc vào lớp tương ứng (istream ostream), đối thứ để date cần thao tác, hàm nhập đối phải đối tham chiếu Tương tự, đối dòng nhập xuất phải khai báo tham chiếu Các hàm có giá trị trả lại tham chiếu đến dòng nhập/xuất tương ứng Việc trả lại tham chiếu cho phép ta gọi nối tiếp phép tốn, ví dụ: cin >> a >> b >> c …
• Các phép tốn tự tăng, giảm viết thành hàm, tương ứng với tăng (giảm) trước sau Date operator++( Date& date)
2 {
3 long new_days = DtoN(date); new_days ++;
5 date = NtoD( new_days ); return date;
7 }
9 Date operator++( Date &date , int n) 10 {
11 Date old_date = date; 12 ++ date;
13 return old_date ; 14 }
(167)dành
cho
hội
đồng
nghiệm
thu
6.3 Dạng khuôn mẫu hàm lớp 159
Trong dạng date cần khai báo tham chiếu giá trị thay đổi Ngoài ra, hai hàm trả lại date kết (để tham gia vào biểu thức) Với tăng trước giá trị trả lại date tăng 1, với tăng sau giá trị trả lại date cũ (chưa tăng) Ngoài danh sách đối, với tăng trước viết bình thường, tăng sau để phân biệt danh sách đối có thêm đối ngun (khơng sử dụng)
Toàn cài đặt lớp Date cho phụ lục B1 Phụ lục B2 trình bày ví dụ khác cài đặt lớp đa thức với phép toán + (cộng), - (trừ đảo dấu), * (nhân), >> (nhập), << (xuất) tính giá trị đa thức Chương trình nhập đa thức: P, Q, R, S Sau tính đa thức: F = -(P + Q) * (R - S) Cuối tính giá trị F(x), với x số thực nhập từ bàn phím
Chú ý:
• Khi dùng hàm tốn tử phép tốn C++, ta kết hợp nhiều phép tốn để viết cơng thức phức tạp Cũng cho phép dùng dấu ngoặc tròn để quy định thứ tự thực phép tính Thứ tự ưu tiên phép tính tuân theo quy tắc ban đầu C++ Chẳng hạn phép * / có thứ tự ưu tiên cao so với phép + –
• Việc định nghĩa hàm tốn tử (chồng phép tốn) thực chất khơng sử dụng cho lớp, mà cịn sử dụng độc lập, ví dụ cho kiểu liệu mảng, xâu, cấu trúc chẳng hạn …
6.3 Dạng khuôn mẫu hàm lớp
Thơng thường thuật tốn dùng để giải vấn đề với nhiều kiểu liệu khác Tuy nhiên, lập trình cổ điển với kiểu liệu ta phải viết hàm khác (chỉ để khai báo kiểu đối kiểu giá trị trả lại hàm) để thể thuật toán Điều gây lãng phí cơng sức, C++ cho phép định nghĩa tên gọi làm kiểu mẫu (template) hàm viết theo tên mẫu Khi gọi hàm tên kiểu thực thay tên mẫu trước hàm thực Nói cách khác, lúc kiểu tham đối hàm mẫu Tóm lại, ta viết hàm với kiểu mẫu để dùng chung cho kiểu liệu khác Các hàm viết gọi khuôn mẫu hàm (hoặc hàm mẫu) trình bày mục 4.6.2 chương
Cũng tương tự, thông thường thành viên lớp (dữ liệu hàm) khai báo cố định trước kiểu Kỹ thuật khn mẫu nói cho phép ta xây dựng lớp tổng quát với kiểu kiểu mẫu sử dụng lớp kiểu mẫu thay kiểu cụ thể tùy theo đối tượng
6.3.1 Khai báo kiểu mẫu
Để dùng tên mẫu thay cho kiểu cụ thể, ta cần định nghĩa trước cú pháp: template <class T> // T: tên kiểu mẫu
hoặc
template <typename T>
(168)dành
cho
hội
đồng
nghiệm
thu
160 Các kiểu liệu trừu tượng
ý: nghĩa từ khóa class khơng liên quan đến lớp đối tượng (T lớp), đơn danh từ chung để loại, kiểu, lớp, họ, tập … )
6.3.2 Sử dụng kiểu mẫu
Dưới ví dụ lớp đặc tả cặp giá trị (kiểu tổng quát T) Mỗi đối tượng lớp có giá trị kiểu Ngồi hàm tạo khơng đối, lớp có phương thức :
• Phương thức Display (để hiển thị đối tượng hình) khơng đối, không giá trị trả lại Được cài đặt trực tiếp bên khai báo lớp
• Phương thức Set (để gán giá trị cho đối tượng) có hai đối kiểu T Không giá trị trả lại Được cài đặt trực tiếp bên khai báo lớp
• Phương thức getMax (để lấy giá trị lớn đối tượng) khơng đối, có giá trị trả lại kiểu T Được cài đặt bên ngồi khai báo lớp
• Phương thức Swap (để tráo đổi hai giá trị đối tượng) không đối, không giá trị trả lại Được cài đặt bên ngồi khai báo lớp
Chương trình tạo đối tượng nguyên kí tự lệnh khai báo:
Pair <int> my_object ; Pair <char> your_object ;
Sau khởi tạo, chương trình in giá trị lớn đối tượng thứ tự tráo đổi chúng # include <iostream >
2 using namespace std;
4 template <class T> class Pair
6 {
7 public:
8 Pair () { value1 = value2 = 0; }
9 void Set(T first , T second ) { value1 = first ; value2 = second ; } 10 T getMax () ;
11 void Swap ();
12 void Display () { cout << "(" << value1 << ", " << value2 << ")" ; } 13 private:
14 T value1 , value2 ; 15 };
16
17 template <class T> 18 T Pair <T >:: getMax () 19 {
20 T res;
21 res = ( value1 > value2 ) ? value1 : value2 ; 22 return res;
23 } 24
(169)dành
cho
hội
đồng
nghiệm
thu
6.3 Dạng khuôn mẫu hàm lớp 161
28 T temp;
29 temp = value1 ; value1 = value2 ; value2 = temp; 30 }
31
32 int main () 33 {
34 Pair <int> my_object ; my_object Set (3, 5);
35 cout << "My pair of integers is " ; my_object Display () ; cout << " and " <<
my_object getMax () << " is greatest ";
36 my_object Swap ();
37 cout << "They was swapped to " ; my_object Display () ; cout << endl; 38
39 Pair <char> your_object ; your_object Set('B', 'D');
40 cout << "Your pair of characters is " ; your_object Display () ; cout << " and "
<< your_object getMax () << " is greatest ";
41 your_object Swap ();
42 cout << "They was swapped to " ; your_object Display () ; cout << endl; 43
44
45 return 0; 46 }
Hình 6.34: Mẫu lớp Pair Qua ví dụ trên, ta rút số nguyên tắc:
• Cần khai báo tên kiểu mẫu (template <class T>) trước định nghĩa lớp
• Kiểu mẫu (T) thay cho vị trí xuất kiểu cụ thể mà lập trình viên muốn tổng qt hóa thành kiểu mẫu (trừ biến cố định không liên quan biến đếm, biến chỉ số kiểu nguyên, kiểu số π double … ) Ngoài ra, yếu tố khác không bị thay đổi viết với kiểu cụ thể
• Bất kỳ phương thức cài đặt bên định nghĩa lớp (cũng giống hàm thơng thường) có bổ sung cần ý: Trước phương thức có khai báo mẫu (ví dụ: template <class T>) tên lớp phải kèm kiểu mẫu <T> (ví dụ: void Pair<T>::Swap())
• Từ tên lớp với kiểu mẫu Pair<T>, ta “chuyên biệt hóa” tên lớp thành nhiều lớp khác với tên kiểu cụ thể Pair<int>, Pair<char> để sử dụng vào mục đích cụ thể, khai báo đối tượng (Pair<int> my_object; Pair<char> your_object;), khai báo tham đối đối tượng int Sum(const Pair<int> &pair_obj)
Các kiểu (lớp) chuyên biệt hóa đặt thành tên kiểu để thuận lợi cho sử dụng như:
typedef Pair <char> Pair_of_Characters ; // đặt lại tên cho Pair<char>
(170)dành
cho
hội
đồng
nghiệm
thu
162 Các kiểu liệu trừu tượng
6.3.3 Một số dạng mở rộng khai báo mẫu
Trong khai báo template, ta nhận thấy tương tự mặt hình thức kiểu mẫu (T) tham đối khai báo hàm Điểm khác biệt đối hàm giá trị, “đối” template kiểu kể hàm (T xem tham đối hình thức cịn int, char … tham đối thực sự) Dựa liên tưởng ta thấy “danh sách đối” template đa dạng danh sách đối hàm, với dạng mở rộng như:
template <class T> : đối kiểu
template <class T, class U> : đối gồm hai kiểu (xem thêm phần khuôn mẫu hàm, mục 4.6.3)
template <class T, int N> : kiểu số
template <class T = char> : kiểu mặc định char
template <double Tfunction (int)> : hàm
(171)dành
cho
hội
đồng
nghiệm
thu
6.3 Dạng khuôn mẫu hàm lớp 163
Bài tập
1 Trong khởi tạo giá trị cho cấu trúc sau, khởi tạo đúng: struct S1 {
int day , month , year; } var1 = {2, 3} ;
struct S2 {
char name [10]; S1 birthday ;
} var2 = {"LyLy", 1, 2, 3};
struct S3 { S2 student ;
double mark;
} var3 = {{{" Coccoc ", {4 ,5 ,6}} , 7};
(a) S1 S2 (b) S2 S3 (c) S3 S1 (d) Cả
2 Đối với kiểu cấu trúc, cách gán không phép: (a) Gán hai biến cho
(b) Gán hai phần tử mảng (kiểu cấu trúc) cho
(c) Gán phần tử mảng (kiểu cấu trúc) cho biến (cấu trúc) ngược lại (d) Gán hai mảng cấu trúc số phần tử cho
3 Cho số phức dạng cấu trúc gồm thành phần thực ảo Viết chương trình nhập số phức in tổng, tích, hiệu, thương chúng
4 Cho phân số dạng cấu trúc gồm thành phần tử mẫu Viết chương trình nhập phân số, in tổng, tích, hiệu, thương chúng dạng tối giản
5 Tính số ngày qua kể từ đầu năm ngày Qui ước ngày khai báo dạng cấu trúc để đơn giản năm tính 365 ngày tháng có 30 ngày Nhập ngày tháng năm dạng cấu trúc Tính xác (kể năm nhuận) số ngày
qua kể từ ngày 1/1/1 ngày Tính khoảng cách ngày tháng
8 Hiện thứ ngày đó, biết ngày 1/1/1 thứ hai Hiện thứ ngày đó, biết ngày 1/1/2000 thứ hai
(172)dành
cho
hội
đồng
nghiệm
thu
164 Các kiểu liệu trừu tượng
11 Hãy nêu ý kiến tất thành viên lớp khai báo public: ?, private: ?
Các tập sử dụng chung định nghĩa lớp Bicycle và Automobile
12 Cho định nghĩa lớp Bicycle sau: class Bicycle
{
public:
void set(double the_price , char the_color ); // thiết lập giá trị tương ứng cho đối tượng
double get_price ( ); // trả lại giá tiền đối tượng
char get_color ( ); // trả lại màu đối tượng
private:
double price ;
char color ; };
Hãy:
(a) Bổ sung thêm khai báo hàm tạo không đối vào lớp (b) Cài đặt hàm tạo, set get lớp
13 Cho lớp Bicycle tập khai báo đối tượng:
Bicycle favorite , peugeot ;
Các câu lệnh sau phép / không phép ?
favorite color = 'G'
peugeot set (3000 , 'B') cout << peugeot price;
cout << get_color ( favorite ); cout << peugeot get_color ();
union = favorite ;
14 Để thay câu lệnh peugeot.set(3000, 'B') Bicycle peugeot(3000, 'B'), khai báo Bicycle cần thêm hàm ? Hãy viết hàm
15 Hãy cài đặt hàm (bên ngồi lớp) so sánh màu xe đạp với (lớp Bicycle tập trên)
16 Giả sử lớp ô tô khai báo class Automobile
{
public:
void set(double the_price , char the_color ); // thiết lập giá trị tương ứng cho đối tượng
double get_price ( ); // trả lại giá tiền đối tượng
char get_color ( ); // )trả lại màu đối tượng
private:
double price ;
char color ;
(173)dành
cho
hội
đồng
nghiệm
thu
6.3 Dạng khuôn mẫu hàm lớp 165
Hãy cài đặt hàm (bên lớp) so sánh giá tiền xe đạp (lớp Bicycle) xe ô tô (lớp Automobile)
17 Hãy cài đặt hàm bạn (và điều chỉnh khai báo Bicycle, Automobile cách thích hợp) để tính tỉ lệ giá tiền xe đạp (lớp Bicycle) xe ô tô (lớp Automobile)
18 Các câu lệnh phép / không phép ? if ( mercedes get_price () == kia_morning get_price ())
cout << "Very good" ;
if ( mercedes == kia_morning ) cout << "Wow" ;
19 Hãy cài đặt phép toán so sánh (kí hiệu ==) cho lớp Automoble
(174)dành
cho
hội
đồng
nghiệm
(175)dành
cho
hội
đồng
nghiệm
thu
Chương 7
Con trỏ nhớ
Hiểu sử dụng trỏ quan trọng lập trình C++ Có lý cho vấn đề Thứ nhất, trỏ cung cấp cách thức hữu hiệu để truy cập vào nhớ máy tính Thứ hai, trỏ hỗ trợ cấp phát nhớ động Và cuối cùng, trỏ cải thiện tính hiệu chương trình Con trỏ đặc tính mạnh chứa nhiều rủi ro sử dụng khơng cách Ví dụ việc khơng khởi tạo trỏ làm chương trình bị đổ vỡ Nó nguyên nhân gây nhiều lỗi khác mà khó tìm Trong chương này, đề cập đến khái niệm vấn đề trỏ C++
7.1 Khái niệm trỏ
Con trỏ biến mà lưu giữ địa nhớ.Địa vị trí biến khác nhớ Nhớ lại rằng, nhớ máy tính chia thành ô nhớ (theo bytes) địa hóa biến lưu giữ dãy ô nhớ liên tiếp tùy theo kích cỡ chúng Ví dụ biến chứa địa biến khác biến xem trỏ đến biến thứ hai Hình 7.1
minh họa vấn đề
Địa “trỏ” tới biến xác định nơi chứa biến đó, thay nói tên biến gì.Một biến lưu ví trí có địa 1003 trỏ biến giá trị 0x1003 nhớ
7.2 Biến trỏ
Con trỏ lưu biến gọi biến trỏ Khai báo trỏ bao gồm kiểu sở, dấu * tên biến Dạng tổng quát sau:
type_name *name1, * name2,
trong type_name kiểu sở trỏ kiểu mà hợp lệ Tên của biến trỏ rõ name1, name2 Dấu * phải đặt trước tên biến trỏ
Ví dụ:
double *pointer1 , * pointer2 ;
(176)dành
cho
hội
đồng
nghiệm
thu
168 Con trỏ nhớ
1003 1000
1001 1002 1003 1004 1005 1006
Memory
Memory address
Variable in memory
Hình 7.1: Biến trỏ đến biến khác nhớ yêu cầu kiểu trỏ khác tương ứng
int *p1 , *p2 , v1 , v2 , a[50];
Ở đây, dấu * phải đặt trước biến trỏ Trong khai báo trên, p1, p2 trỏ trỏ đến biến kiểu nguyên, v1, v2 biến nguyên thông thường a mảng nguyên
Sau khai báo trỏ, để trỏ đến biến (ví dụ cho p1 trỏ đến v1), ta dùng cú pháp: p1 = &v1;
Ở đây, & phép toán lấy địa Câu lệnh lấy địa v1 đặt vào p1 (p1 chứa địa v1, tức p1 trỏ đến v1) Lưu ý, p1 phải trỏ kiểu với v1 (int)
Chúng ta có cách để tham chiếu đến v1 Chúng ta gọi v1 gọi thơng qua trỏ (p1) *p1 Đây dấu * sử dụng để khai báo p1 có ý nghĩa khác Khi dấu * sử dụng theo cách này, thường gọi toán tử tham chiếu lại (dereferencing) Việc sử dụng v1 hay *p1 tương đương, tức thay v1 *p1 ngược lại đâu biểu thức
Trong C++, cách mà thông qua biến trỏ p1 giá trị v1 *p1 Đây dấu * sử dụng để khai báo p1 có ý nghĩa khác Khi dấu * sử dụng theo cách này, thường gọi tốn tử tham chiếu lại (dereferencing)
Ví dụ:
v1 = 0; p1 = &v1; *p1 = 42;
cout << v1 << endl; cout << *p1 << endl;
Đoạn mã cho kết sau:
(177)dành
cho
hội
đồng
nghiệm
thu
7.2 Biến trỏ 169
Chương trình hình 7.2 minh họa việc sử dụng khai báo trỏ Trình bày cách in hình giá trị trỏ giá trị mà trỏ trỏ tới
1 // Chuong trinh minh hoa viec thao tac voi tro # include <iostream >
3 using namespace std; int main(void)
5 {
6 int x = 100; int *p1 , *p2; p1 = &x; p2 = p1;
10 cout << p2 << endl; // in dia chi cua x, khong phai gia tri x 11 cout << *p2; // in gia tri x
12 return 0; 13 }
Hình 7.2: Sử dụng khai báo trỏ
Output chương trình Hình 7.2:
0 x22ff44 100
Con trỏ trỏ đến mảng cách gán địa mảng (cũng địa phần tử mảng) cho trỏ Vì tên mảng địa mảng nên câu lệnh gán là:
p2 = a; // khơng có dấu & // hoặc
p2 = &a[0]; // có dấu &
Việc tham chiếu đến phần tử mảng a thông qua trỏ nói đến phần sau Phép tốn trỏ
Các phép toán với trỏ tham gia làm việc trỏ có kiểu sở (để ngắn gọn từ trở ta gọi hai trỏ kiểu)
Phép gán
Hai trỏ kiểu gán cho phép toán gán (=) Hiệu hai trỏ trỏ đến nơi nhớ
Ví dụ:
double *p1 , *p2 , v; p1 = &v;
p2 = p1; v = 3.14; *p1 = v * v;
cout << *p2; // 9.8596
Phép tốn số học
Chỉ có phép tốn số học trỏ là: cộng trừ Để hiểu phép toán số học thao tác với trỏ, ký hiệu p1 trỏ kiểu short giá trị thời 2000 Giả sử kiểu short bytes, đó, câu lệnh sau:
(178)dành
cho
hội
đồng
nghiệm
thu
170 Con trỏ nhớ
thì p1 2002, khơng phải 2001 p1 tăng lên theo kích thước kiểu mà trỏ trỏ đến Nghĩa p1 trỏ tời số kiểu short tiếp theo.Điều tương tự giảm (trừ) trỏ Ví dụ trên, p1 có giá trị 2000 câu lệnh:
p1 cho giá trị p1 1998, trỏ tới số kiểu short đứng kề trước số
Từ ví dụ trên, cách tổng quát, quy tắc sau áp dụng cho trỏ số học Mỗi trỏ tăng lên, trỏ đến vị trí nhớ phần tử kiểu với kiểu trỏ Mỗi lần giảm đi, đến vị trí phần tử trước kiểu với kiểu trỏ Tất trỏ khác tăng giảm theo kích thước kiểu liệu mà chúng trỏ đến Cách tiếp cận đảm bảo trỏ trỏ đến phần tử thích hợp kiểu liệu với
Hình7.3 minh họa khái niệm Chúng ta không giới hạn cho tốn tử tăng giảm Ví dụ, thêm trừ số nguyên từ trỏ Ví dụ, câu lệnh:
p1 = p1 + 12;
thì p1 trỏ đến phần tử thứ 12 với kiểu trùng với kiểu p1
3000 ch
3001 3002 3003 3004 3005 ch+1
ch+2 ch+3 ch+4 ch+5
Memory
char *ch=3000; int *i=3000;
i
i+1
i+2
Hình 7.3: Phép toán số học với trỏ liên quan đến kiểu trỏ
Như vậy, phép toán tăng, giảm trỏ cho phép làm việc thuận lợi mảng, mảng dãy phần tử kiểu xếp liên tục nhớ Nếu trỏ trỏ đến mảng (tức chứa địa mảng), việc tăng trỏ lên đơn vị dịch chuyển trỏ trỏ đến phần tử thứ hai trỏ p phần tử thứ k mảng p + h trỏ đến phần tử thứ k + h mảng
Từ đó, ta cho trỏ chạy từ đầu đến cuối mảng cách tăng trỏ lên đơn vị câu lệnh for sử dụng trỏ để in phần tử mảng
Ví dụ:
int a[5] = { 1, 2, 3, 4, }, *p;
p = a; // hoặc p = &a[0], cho p trỏ đến mảng a
for (int index = 0; index < 5; index ++) {
(179)dành
cho
hội
đồng
nghiệm
thu
7.3 Cấp phát nhớ động 171
}
hoặc cho p chạy từ đầu đến cuối mảng for (int index = 0; index < 10; index ++) {
cout << *p ; p++;
}
Thơng qua ví dụ , p trỏ trỏ đến mảng a, ta đồng nhất:
a[i] = p[i] = *(p + i) = *(a + i)
Việc cộng trỏ vô nghĩa, nhiên phép trừ hai trỏ p q phép p q trỏ trỏ đến phần tử dãy liệu nhớ (ví dụ trỏ đến mảng liệu) Khi đó, hiệu p - q số thành phần p q (không phải hiệu địa chỉ) Ví dụ p = q + 12 p – q = 12 Nếu p q trỏ short, p có địa 200 q có địa 208 Khi p - q = �4 q - p = (4 số thành phần short từ địa 200 đến 208)
Phép toán số học
Các phép toán so sánh áp dụng trỏ, thực chất so sánh địa hai nơi trỏ trỏ Thông thường, phép so sánh <, <=, >, >= áp dụng cho hai trỏ trỏ đến phần tử mảng liệu Thực chất phép so sánh so sánh số phần tử trỏ trỏ
double a[10] , *p, *q ;
p = a ; // p trỏ đến mảng (tức p trỏ đến a[0] q = &a[3] ; // q trỏ đến phần tử thứ (a[3]) cout << (p < q) ; // 1
cout << (p + == q) ; // 1
cout << (p > q - 1) ; // 0
cout << (p >= q - 2) ; // 0
for (p=a ; p <a+100; p++) cout << *p ; // in toàn mảng a
Chú ý: Như phần trước đề cập tên mảng (ví dụ mảng a trên) địa mảng (cũng địa phần tử a[0]) Do vậy: - a + k địa phần tử a[k], a + 100 địa phần tử a[100] - *a giá trị phần tử a[0], *(a + k) giá trị phần tử a[k] - Nếu p trỏ trỏ đến a p a chứa giá trị (cùng địa phần tử a[0]), nhiên p biến, thay đổi (ví dụ tăng p thành p++ p trỏ đến phần tử a[1]) cịn a hằng, a + địa a[1] tăng a tăng p (ví dụ khơng thể viết a++) đặt lại p = p + 100 đặt lại a = a + 100
7.3 Cấp phát nhớ động
(180)dành
cho
hội
đồng
nghiệm
thu
172 Con trỏ nhớ
nguyên chứa 1000 số ngun bytes nhớ có vùng nhớ liên tục 4000 bytes để chứa liệu mảng Khi dù chương trình ta nhập vào mảng làm việc với vài số phần mảng rỗi cịn lại khơng sử dụng vào mục đích khác Đây hạn chế thứ kiểu mảng Ở hướng khác, lần chạy chương trình ta lại cần làm việc với 1000 số nguyên Khi đó, vùng nhớ mà chương trình dịch dành cho mảng khơng đủ để sử dụng chương trình bị lỗi Đây hạn chế thứ hai mảng khai báo trước
Khắc phục hạn chế kiểu mảng, ngơn ngữ lập trình cho phép cấp phát động Nghĩa nhớ cấp phát đúng, đủ trình chạy chương trình theo yêu cầu người sử dụng Nhờ vậy, có đủ số nhớ để làm việc mà tiết kiệm nhớ, khơng dùng ta thu hồi (giải phóng) số nhớ để chương trình sử dụng vào việc khác Thơng qua trỏ ta làm việc với địa vùng cấp phát Cách thức bố trí nhớ gọi cấp phát động
Một vùng nhớ đặc biệt nhớ gọi heap nơi dùng để cấp phát động Bất biến cấp phát động tạo sử dụng vùng nhớ Khi vùng nhớ heap sử dụng hết, việc yêu cầu cấp phát thêm bị lỗi
Để cấp phát nhớ cho biến vùng heap ta sử dụng toán tử new Toán tử tạo biến cấp phát động với kiểu khai báo trả trỏ trỏ đến biến Ví dụ, đoạn chương trình sau tạo biến cấp phát động với kiểu MyType gán cho biến trỏ p trỏ đến biến này:
MyType *p; p = new MyType ;
Trong C++ chuẩn, khơng đủ nhớ có sẵn vùng heap để tạo biến cấp phát động toán tử new mặc định chấm dứt chương trình
Kích cỡ vùng nhớ heap thay đổi máy tính tùy thuộc vào thực C++.Nó thường lớn, chương trình khiêm tốn khơng có khả sử dụng tất nhớ heap.Tuy nhiên, chí với chương trình nhỏ, thói quen tốt cho việc thực hành nên giải phóng nhớ cấp phát động khơng cịn dùng đến tốn tử delete Lệnh hủy vùng nhớ cấp phát động cho trỏ p (trong câu lệnh p = new MyType; trên) trả vùng nhớ sử dụng p cho vùng nhớ heap
delete p;
Sau gọi toán tử delete, giá trị p không xác định p xem biến chưa khởi tạo
7.4 Con trỏ mảng động
7.4.1 Biến mảng biến trỏ
Trong phần trên, thấy biến mảng thực biến trỏ.Ở đây, sẽthấy thêm tính hữu hiệu công cụ trỏ thao tác với mảng động Mảng động mảng mà kích cỡ khơng cố định ta viết chương trình mà xác định chương trình chạy
Nhắc lại quan hệ trỏ mảng Cho hai biến p a mô tả kiểu biến : int a[10];
(181)dành
cho
hội
đồng
nghiệm
thu
7.4 Con trỏ mảng động 173
Bởi a trỏ trỏ tới biến kiểu int nên giá trị a gán cho biến trỏ p sau :
p = a ;
Sau phép gán này, p trỏ tới nhớ mà a trỏ, p[0], p[1], …, p[9] biến a[0], a[1], …, a[9] tương ứng Sử dụng ngoặc vuông số với biến trỏ hoàn toàn giống biến mảng truy cập giá trị mảng Tuy nhiên, thay đổi giá trị trỏ mảng a :
int *p2 ;
a = p2; //Không hợp lệ
1 # include <iostream > using namespace std;
4 int main( ) {
6 int *p; int a [10];
9 for (int i = 0; i < 10; i++) 10 a[i] = i;
11
12 p = a; 13
14 for (int index = 0; index < 10; index ++) 15 cout << p[ index ] << " ";
16 cout << endl; 17
18 for (int index = 0; index < 10; index ++) 19 p[index ] = p[ index ] + 1;
20
21 for (int index = 0; index < 10; index ++) 22 cout << a[ index ] << " ";
23 cout << endl; 24
25
26 return 0; 27 }
Hình 7.4: Sử dụng trỏ tên mảng
Output chương trình Hình 7.5:
0 9 1 10 # include <iostream > using namespace std;
4 int main( ) {
(182)dành
cho
hội
đồng
nghiệm
thu
174 Con trỏ nhớ
9 for (int i = 0; i < 10; i++) 10 a[i] = i;
11
12 p = a; 13
14 for (int index = 0; index < 10; index ++) 15 cout << p[ index ] << " ";
16 cout << endl; 17
18 for (int index = 0; index < 10; index ++) 19 p[ index ] = p[ index ] + 1;
20
21 for (int index = 0; index < 10; index ++) 22 cout << a[ index ] << " ";
23 cout << endl; 24
25
26 return 0; 27 }
Hình 7.5: Sử dụng trỏ tên mảng
Output chương trình Hình 7.5:
0 9 1 10
7.4.2 Biến mảng động
Như thảo luận trên, mảng khai báo trước (tĩnh) thường gặp phải vấn đề: viết chương trình khơng biết kích thước mảng trước chương trình chạy Ví dụ, mảng danh sách sinh viên lớp số sinh viên thay đổi thời gian chạy chương trình Chúng ta thường ước lượng kích thước lớn hy vọng đủ lớn để sử dụng Vì vậy, nẩy sinh hai vấn đề: thừa, lãng phí kích thước mảng lớn, khơng dùng hết thiếu kích thước mảng q nhỏ, chương trình khơng thực trường hợp số phần tử vượt kích thước khai báo trước mảng
Mảng động tránh vấn đề Khi chương trình sử dụng mảng động để lưu danh sách sinh viên số sinh viên lớp đầu vào chương trình mảng động tạo xác với số sinh viên
Tạo sử dụng mảng động đơn giản
Ví dụ, dịng lệnh sau tạo biến mảng động với 10 phần tử kiểu double: double p;
p = new double [10];
Dĩ nhiên, mảng động khơng có tên mảng mảng tĩnh thơng thường Tuy nhiên, ta làm việc với mảng thông qua trỏ p trỏ đến
(183)dành
cho
hội
đồng
nghiệm
thu
7.4 Con trỏ mảng động 175
Chú ý, gọi tốn tử new, kích thước mảng động cho ngoặc vuông theo sau kiểu liệu, ví dụ kiểu liệu double Điều thơng báo cho máy tính kích thước nhớ sử dụng cho mảng động Nếu quên dấu ngoặc vng 10 máy tính cấp phát phần tử kiểu double thay cấp phát cho mảng 10 phần tử kiểu double
Ví dụ chương trình hình 7.5, sử dụng biến mảng động kiểu int kích thước 10
Chương trình hình7.5 xếp danh sách số nguyên (int) Chương trình thực với danh sách kích thước sử dụng mảng động để lưu giữ Kích thước mảng xác định chương trình chạy Người sử dụng nhập vào số cần xếp tốn tử new tạo mảng động với kích thước
Chú ý tốn tử delete xóa biến mảng động a chương trình hình 7.5 Nếu chương trình kết thúc khơng cần tốn tử delete, nhiên chương trình thực việc khác với biến mảng động nên dùng delete để trả lại tự cho phần nhớ cấp phát cho a Thao tác delete với mảng cấp phát động tương tự với biến trỏ khác phải thêm cặp ngoặc vng đóng mở sau:
delete [] a;
1 // Sorts a list of numbers entered at the keyboard # include <iostream >
3 # include <cstdlib > # include <cstddef >
6 using namespace std;
8 void InputArray (int a[], int size); // Input values from the keyboard 10
11 void SortArray (int a[], int size);
12 // Output : The values of a[0] through a[ size�1 ] have been rearranged 13 //so that a[0] <= a[1] <= <= a[ size�1 ].
14
15 int main( ) 16 {
17
18 cout << "This program sorts numbers from lowest to highest \n"; 19
20 int array_size ;
21 cout << "How many numbers will be sorted ? "; 22 cin >> array_size ;
23
24 int *a;
25 a = new int[ array_size ]; 26
27 InputArray (a, array_size ); 28 SortArray (a, array_size ); 29
30 cout << "In sorted order the numbers are :\n"; 31 for (int index = 0; index < array_size ; index ++) 32 cout << a[ index ] << " ";
(184)dành
cho
hội
đồng
nghiệm
thu
176 Con trỏ nhớ
34
35 delete [] a; 36
37 return 0; 38 }
39 // Uses the library
40 void InputArray (int a[], int size) 41 {
42
43 cout << " Enter " << size << " integers \n"; 44 for (int i = 0; i < size; i++)
45 cin >> a[i]; 46 }
47
48 void SortArray (int a[], int size) 49 {
50 for (int i = 0; i < size -1; i++) 51 for (int j = i +1; j < size; j++) 52 if (a[i] > a[j]){
53 int temp = a[i];
54 a[i] = a[j];
55 a[j] = temp;
56
57 }
58 }
Hình 7.6: Mảng cấp phát động
Output chương trình Hình 7.6:
This program sorts numbers from lowest to highest How many numbers will be sorted ? 5
Enter integers 6 4
In sorted order the numbers are: 3 8
7.5 Truyền tham số hàm trỏ
(185)dành
cho
hội
đồng
nghiệm
thu
7.6 Con trỏ hàm 177
1 # include <iostream > using namespace std;
4 void swap(int *x, int *y) {
6 int temp ; temp = *x ; *x = *y ; *y = temp ; 10 }
11
12 int main () 13 {
14 int a = 3, b = 5; 15 swap (&a, &b);
16 cout << "a = " << a << ", b = " << b << endl; // 5, (a,b doi gia tri) 17 return 0;
18 }
Hình 7.7: Truyền tham số trỏ
Output chương trình Hình 7.7:
a = 5, b = 3
7.6 Con trỏ hàm
Một tính đặc biệt khó hiểu mạnh C++ trỏ hàm Thậm chí hàm khơng phải biến có vị trí vật lý nhớ mà gán cho trỏ Địa điểm nhập hàm địa sử dụng hàm gọi Khi trỏ trỏ tới hàm hàm gọi thơng qua trỏ Các trỏ hàm cho phép hàm truyền đối số tới hàm khác
Chúng ta lấy địa hàm cách sử dụng tên hàm không kèm theo ngoặc tham số hàm (điều tương tự cách lấy địa mảng dùng tên mảng mà khơng có số kèm theo) Để xem chúng hoạt động nào, ta xem ví dụ hình 7.8
1 # include <iostream > # include <cstring > # include <cstdlib >
5 using namespace std;
7 void Check (char *a, char *b, int (* cmp)(const char *, const char *));
9 int main () 10 {
11 char s1 [80] , s2 [80];
(186)dành
cho
hội
đồng
nghiệm
thu
178 Con trỏ nhớ
14
15 cout << " Input of s1 string : "; 16 cin getline (s1 ,80);
17
18 cout << " Input of s2 string : "; 19 cin getline (s2 ,80);
20
21 Check (s1 , s2 , p); 22
23
24 return 0; 25 }
26 void Check (char *a, char *b, int (* cmp)(const char *, const char *)) 27 {
28 cout << " Testing for equality " << endl; 29 if(!(* cmp)(a, b)) cout << " Equal " << endl; 30 else cout << "Not Equal " << endl;
31 return; 32 }
Hình 7.8: Ví dụ trỏ hàm so sánh xâu ký tự
Output chương trình Hình 7.8
Input of s1 string : Hello
Input of s2 string : Hello John Testing for equality
Not Equal
Khi hàm check() hình 7.8 gọi, hai trỏ kiểu ký tự trỏ hàm truyền tham số Bên hàm check(), đối số mô tả trỏ kiểu ký tự trỏ hàm Dấu ngoặc bao quanh *cmp cần thiết cho việc biên dịch cách xác Bên hàm check(), biểu thức
(* cmp )(a,b)
sẽ gọi strcmp(), hàm mà trỏ cmp với đối số a b Chú ý rằng, gọi check() cách sử dụng trực tiếp strcmp() sau:
check(s1 ,s2 , strcmp );
Chúng ta mở rộng ví dụ hình 7.8 thành ví dụ hình 7.9 Trong ví dụ này, nhập vào chữ strcmp() truyền qua hàm check() Trái lại, numcmp() truyền vào hàm check() Bởi check() gọi hàm mà truyền vào, sử dụng hàm so sánh khác trường hợp khác
7.7 Lập trình với danh sách liên kết
(187)dành
cho
hội
đồng
nghiệm
thu
7.7 Lập trình với danh sách liên kết 179
1 // Chuong trinh minh hoa ve tro ham
2 // Con tro ham co the tro den ham khac NumCmp () va strcmp () # include <iostream >
4 # include <cstring > # include <cstdlib >
7 using namespace std;
9 void Check (char *a, char *b, int (* cmp)(const char *, const char *)); 10
11 int NumCmp (const char *a, const char *b); 12
13 int main () 14 {
15 char s1 [80] , s2 [80];
16 // int (*p)( const char *, const char *); 17 //p = strcmp ;
18
19 cout << "Input of s1 string : "; 20 cin getline (s1 ,80);
21
22 cout << "Input of s2 string : "; 23 cin getline (s2 ,80);
24
25 if ( isalpha (*s1))
26 Check (s1 , s2 , strcmp ); 27 else
28 Check (s1 ,s2 , NumCmp ); 29
30
31 return 0; 32 }
33 // -34 void Check (char *a, char *b, int (* cmp)(const char *, const char *)) 35 {
36 cout << " Testing for equality " << endl; 37 if(!(* cmp)(a, b)) cout << " Equal " << endl; 38 else cout << "Not Equal " << endl;
39 return; 40 }
41
42 // -43 int NumCmp (const char *a, const char *b)
44 {
45 if (atoi(a) == atoi(b)) return 0; 46 else return 1;
47 }
(188)dành
cho
hội
đồng
nghiệm
thu
180 Con trỏ nhớ
7.7.1 Nút danh sách liên kết
Biến động phổ biến với số kiểu liệu phức tạp liệu kiểu mảng, kiểu cấu trúc hay kiểu lớp Như biết biến động mảng hữu ích liệu kiểu cấu trúc hay kiểu lớp hữu ích theo cách khác Biến động kiểu cấu trúc kiểu lớp thơng thường có nhiều biến thành viên biến trỏ kết nối chúng tới biến động khác Ví dụ, cấu trúc chứa danh sách mua sắm biểu diễn hình7.10
Nút
Một cấu trúc hình 7.10 bao gồm mục vẽ hộp nối với mũi tên Các hộp gọi nút mũi tên thể cho trỏ Mỗi nút hình
7.10 có biến thành viên, biến chứa liệu kiểu chuỗi, biến chứa liệu kiểu số nguyên biến lại chứa liệu kiểu trỏ trỏ đến nút khác kiểu danh sách Các nút
rolls 10
3 jam
tea end marker head
Hình 7.10: Nút trỏ
được thực C++ có kiểu cấu trúc kiểu lớp Ví dụ, định nghĩa kiểu cấu trúc cho nút thể hình7.10, với định nghĩa cho trỏ trỏ tới nút sau:
struct ListNode {
string item;
int count ; ListNode *link; };
typedef ListNode * ListNodePtr ;
(189)dành
cho
hội
đồng
nghiệm
thu
7.7 Lập trình với danh sách liên kết 181
Hộp có tên head hình 7.10 khơng phải nút, mà biến trỏ trỏ đến nút Biến trỏ head khai báo sau:
ListNodePtr head;
Như minh họa, giả sử khai báo trên, tình mơ tả hình 7.10, muốn thay đổi số nguyên nút từ 10 thành 12, ta thực sau:
(* head ) count = 12;
Trong biểu thức bên trái toán tử gán, biến head biến trỏ, nên *head trỏ tới địa nút, cụ thể trường hợp nút chứa chuỗi "rolls" và số nguyên 10 Nút này tham chiếu *head có kiểu cấu trúc; biến thành viên kiểu cấu trúc chứa giá trị kiểu nguyên gọi count, nên (*head).count tên biến nguyên nút đầu tiên Tuy nhiên, toán tử ” ” có độ ưu tiên cao tốn tử ” * ”, khơng có cặp đóng mở ngoặc ” () ” tốn tử ” ” thực (và phát sinh lỗi) Phần tiếp theo mơ tả ký hiệu ngắn giải vấn đề
Trong C++ có tốn tử sử dụng với trỏ để đơn giản kí hiệu cho biến thành viên cấu trúc lớp Toán tử ” -> ” kết hợp hành động toán tử tham chiếu ” * ” toán tử ” ” tới thành viên cấu trúc đối tượng cụ thể trỏ bởi trỏ Ví dụ, câu lệnh gán thay đổi số nguyên nút viết đơn giản sau:
head ->count = 12;
Câu lệnh gán câu lệnh gán trước có ý nghĩa giống nhau, câu lệnh thường sử dụng
Chuỗi nút thay đổi từ "rolls" thành "bagels" với câu lệnh sau:
head ->item = " bagels ";
Kết thay đổi tới nút danh sách mơ tả hình7.11 Nhìn vào nút cuối danh sách, ta thấy nút có từ NULL vị trí lẽ trỏ Trong hình 7.10, vị trí điền với cụm từ ”end marker”, ”end marker” biểu thức C++ Trong chương trình C++, sử dụng số NULL hằng số đặc biệt dấu chấm hết báo hiệu kết thúc danh sách liên kết
Hằng số NULL sử dụng cho hai mục đích khác (nhưng thường trùng nhau) Thứ nhất, sử dụng để cung cấp giá trị cho biến trỏ, không biến khơng có bất giá trị Điều ngăn cản tham chiếu tới vùng nhớ, NULL khơng phải địa vùng nhớ Thứ hai, sử dụng điểm đánh dấu kết thúc Một chương trình duyệt qua danh sách nút hình7.11 chương trình tới nút có chứa NULL cho biết tới nút cuối danh sách.
Một trỏ thiết lập giá trị NULL sử dụng tốn tử gán sau, khai báo biến trỏ there khởi tạo với giá trị NULL :
double *there = NULL;
(190)dành
cho
hội
đồng
nghiệm
thu
182 Con trỏ nhớ Before
rolls 10
jam
tea NULL
head head
After
jam
tea NULL bagels
12
Hình 7.11: Truy cập liệu nút
void func(int *p);
void func(int i);
Hàm gọi gọi func(NULL) ? Vì NULL số nên hai hai hàm trên có giá trị ngang C++11 giải vấn đề cách đưa thêm số nullptr, nullptr số nguyên số chữ sử dụng để đại diện cho trỏ null Sử dụng nullptr nơi sử dụng NULL cho trỏ Ví dụ, viết:
double *there = nullptr ;
Danh sách liên kết
Trong phần trước, sử dụng kiểu mảng để lưu trữ danh sách phần tử Việc sử dụng mảng có điểm thuận lợi phần tử truy nhập trực tiếp, nhanh thông qua số (cách truy nhập gọi truy nhập ngẫu nhiên (random access) ngược với cách truy nhập (sequential access) nhiều thời gian phải duyệt qua hết phần tử đứng trước định vị phần tử cần truy nhập) Ưu điểm kiểu mảng có phần tử có kích thước (vì kiểu) xếp kề liên tục, “thẳng hàng” nhớ Một số nhược điểm mảng (tĩnh) nêu khắc phục mảng động Tuy nhiên, việc sử dụng mảng động cịn có nhược điểm: ví dụ, cần tạo mảng động với 1000 phần tử số dạng int (4 bytes), chương trình tìm heap phần nhớ rỗi số lượng 4000 bytes “kề liên tục” để cấp phát cho mảng Đơi tổng lượng bytes rỗi heap lớn 4000 bytes, chúng nằm “rải rác” khơng kề nhau, việc cấp phát thất bại
(191)dành
cho
hội
đồng
nghiệm
thu
7.7 Lập trình với danh sách liên kết 183
Khái niệm danh sách liên kết
Cũng tương tự kiểu mảng, danh sách liên kết dùng để lưu trữ danh sách, tập hợp phần tử kiểu xếp theo thứ tự xác định Tuy nhiên, danh sách liên kết khác với kiểu mảng đặc điểm quan trọng sau:
- Không cần khai báo trước kích thước (tối đa) danh sách Trong trình hoạt động, cần thêm phần tử vào danh sách, chương trình xin cấp phát thêm nhớ vừa đủ để lưu phần tử Ở chiều ngược lại, phần tử bị loại khỏi danh sách, chương trình đề nghị hệ điều hành thu hồi lại phần nhớ để sử dụng vào việc khác Như vậy, thời điểm bất kỳ, số nhớ bị chiếm dụng luôn với số phần tử danh sách, khác với kiểu mảng số nhớ bị chiếm dụng luôn số
- Khi cần thêm phần tử vào danh sách chương trình xin cấp phát nhớ, phần tử danh sách liên kết nằm vị trí nhớ không nằm theo trật tự kề liên tiếp kiểu mảng
- Danh sách liên kết không bị xảy tình trạng đầy mảng, lệ thuộc vào dung lượng nhớ thực máy tính
Các phần tử danh sách liên kết không nằm kề liên tiếp nhớ, làm để xác định vị trí phần tử thứ tự chúng Để làm điều này, trang bị thêm cho phần tử danh sách trỏ Nhiệm vụ trỏ tạo mối liên kết có thứ tự phần tử danh sách cách cho trỏ phần tử trỏ đến (chứa địa của) phần tử đứng kề sau Như vậy, xuất phát từ phần tử danh sách (ví dụ A), nhìn vào trỏ A ta tìm phần tử đứng kề sau A (ví dụ B), thông qua trỏ B ta tìm phần tử C … cuối thông qua dẫn đường trỏ ta dễ dàng tìm thấy tồn phần tử lại danh sách theo thứ tự trên, đến gặp phần tử chứa trỏ với giá trị NULL phần tử cuối
Một danh sách với phần tử có trỏ trỏ đến phần tử đứng sau gọi danh sách liên kết (đơn) minh họa hình7.11 Một danh sách liên kết danh sách nút, nút có biến thành viên trỏ trỏ tới nút danh sách Nút danh sách liên kết gọi head, lý biến trỏ trỏ tới nút đặt tên head Lưu ý trỏ head thân khơng đứng đầu danh sách mà trỏ tới nút danh sách Nút cuối khơng có tên đặc biệt nhưng có tính chất đặc biệt, NULL giá trị biến trỏ thành viên Để kiểm tra xem nút có phải nút cuối hay không, cần kiểm tra xem biến trỏ nút có NULL hay không.
Mục tiêu phần xây dựng số hàm thao tác với danh sách liên kết Để đơn giản kí hiệu, ta sử dụng kiểu liệu nút đơn giản so với nút hình 7.11 Những nút chứa số nguyên trỏ Định nghĩa nút trỏ sau:
struct Node {
int data; Node *link; };
(192)dành
cho
hội
đồng
nghiệm
thu
184 Con trỏ nhớ
Bây giờ, xem xét việc bắt đầu xây dựng danh sách liên kết với nút có kiểu liệu Đầu tiên, khai báo biến trỏ có tên head, biến trỏ tới nút danh sách liên kết chúng ta, khai báo sau:
NodePtr head;
Để tạo nút đầu tiên, sử dụng toán tử new để tạo biến động mới, biến động trở thành nút danh sách liên kết:
head = new Node;
Sau đó, ta gán giá trị tới biến thành viên nút này:
head ->data = 3; head ->link = NULL;
Chú ý rằng, biến trỏ nút phải thiết lập NULL, nút cũng nút cuối danh sách (đồng thời nút danh sách) Ở giai đoạn này, danh sách liên kết sau:
3 NULL head
Chèn nút vào vị trí đầu danh sách
Trong phần này, giả sử danh sách liên kết chứa sẵn nhiều nút, ta xây dựng hàm có chức chèn thêm nút khác vào danh sách Tham số cho hàm chèn tham số gọi tham chiếu cho biến trỏ trỏ tới nút đầu danh sách liên kết Tham số lại đưa vào số nguyên lưu trữ nút Khai báo hàm cho hàm chèn sau:
void head_insert ( NodePtr & head , int the_number );
Để chèn nút vào danh sách liên kết, ta sử dụng toán tử new để tạo nút Dữ liệu sau chép vào nút chèn vào đầu danh sách Khi chèn nút theo cách này, nút trở thành nút danh sách nút cuối Sau biến động khơng có tên, phải sử dụng biến trỏ cục để trỏ tới nút Nếu gọi biến trỏ cục temp_ptr, nút gọi *temp_ptr Các bước được tổng hợp lại sau:
Giả mã cho hàm head_insert :
1 Tạo biến động trỏ tới temp_ptr (Biến động nút Nút này có thể gọi *temp_ptr ).
2 Gán liệu cho nút
(193)dành
cho
hội
đồng
nghiệm
thu
7.7 Lập trình với danh sách liên kết 185
4 Biến trỏ tên head trỏ tới nút
Hình7.13 chứa lưu đồ thuật tốn Bước lưu đồ thể câu lệnh C++ sau:
temp_ptr ->link = head; head = temp_ptr ;
Hàm đầy đủ định nghĩa hình 7.12 struct Node
2 {
3 int data; Node *link; };
6
7 typedef Node* NodePtr ;
9 // Dau vao: Bien cho tro head tro toi nut dau tien cua danh sach lien ket. 10 // Dau ra: Mot nut moi chua the_number duoc them vao tai vi tri dau tien 11 // cua danh sach lien ket
12 void Head_Insert ( NodePtr & head , int the_number ); 13
14 void Head_Insert ( NodePtr & head , int the_number ) 15 {
16 NodePtr temp_ptr ; 17 temp_ptr = new Node; 18
19 temp_ptr ->data = the_number ; 20
21 temp_ptr ->link = head; 22 head = temp_ptr ;
23 }
Hình 7.12: Hàm chèn thêm nút vào đầu danh sách liên kết
Danh sách liên kết đặt tên tên trỏ trỏ tới nút đầu danh sách, danh sách rỗng khơng có nút đầu Để cụ thể danh sách rỗng, ta sử dụng trỏ NULL Nếu biến con trỏ head coi trỏ tới nút đầu danh sách liên kết muốn danh sách là rỗng, ta cần thiết lập giá trị trỏ head sau:
head = NULL;
(194)dành
cho
hội
đồng
nghiệm
thu
186 Con trỏ nhớ
12 ?
3 NULL temp_ptr
15 head
1 Set up new node
12
3 NULL temp_ptr
15
head Added
2 temp_ptr->link = head;
12
3 NULL temp_ptr
15
3 head = temp_ptr
Changed head
4 After function call
12
3 NULL
15 head
Hình 7.13: Thêm nút vào danh sách liên kết Hiện tượng: Mất nút
Ta viết định nghĩa hàm head_insert (hình 7.12) sử dụng biến trỏ head để xây dựng nút mới, thay sử dụng biến trỏ cục temp_ptr Nếu thử việc đó, ta bắt đầu hàm sau:
head = new Node;
head ->data = the_number ;
Tại thời điểm nút xây dựng, chứa liệu trỏ tới trỏ head Tất việc lại gắn phần lại danh sách tới nút việc thiết lập trỏ thành viên đưa bên để trỏ sau trỏ tới nút trước nút danh sách sau:
head ->link
(195)dành
cho
hội
đồng
nghiệm
thu
7.7 Lập trình với danh sách liên kết 187
12 ? head
3 NULL
15
Lost nodes
Hình 7.14: Nút bị danh sách liên kết
Một chương trình bị nút đơi cho ”rị rỉ nhớ”, rị rỉ nhớ dẫn tới tới kết chương trình chạy khỏi nhớ, nguyên nhân khiến chương trình bị chấm dứt bất thường Để tránh bị nút, chương trình phải ln giữ vài trỏ trỏ tới đầu danh sách, thường trỏ biến trỏ head.
Tìm kiếm danh sách liên kết
Phần này, xây dựng hàm tìm kiếm danh sách liên kết để xác định vị trí nút cụ thể.Chúng ta sử dụng nút kiểu sử dụng mục trước, gọi là Node (định nghĩa nút kiểu trỏ đưa hình7.12) Hàm xây dựng có hai đối số: cho danh sách liên kết, số nguyên mà muốn xác định vị trí nút Hàm trả trỏ trỏ tới nút chứa số ngun Nếu khơng có nút thỏa mãn thì hàm trả trỏ NULL Bằng cách này, chương trình kiểm tra xem liệu số nguyên có danh sách kiểm tra để xem hàm trả giá trị trỏ khác NULL hay không? Khai báo hàm thích cho hàm sau:
NodePtr search ( NodePtr head , int target );
//Điều kiện trước: Con trỏ head trỏ tới đầu của
//danh sách liên kết Biến trỏ nút cuối NULL.
//Nếu danh sách rỗng, head NULL Trả con
//trỏ trỏ tới nút đầu chứa target Nếu khơng cónút nào
//thỏa mãn, hàm trả NULL.
Chúng ta sử dụng trỏ cục gọi here di chuyển qua danh sách để tìm kiếm target Cách để di chuyển quanh danh sách liên kết, hay cấu trúc liệu khác gồm có nút trỏ dựa vào trỏ Vậy nên bắt đầu với trỏ here trỏ tới nút danh sách di chuyển trỏ từ nút tới nút theo sau trỏ nút, kĩ thuật biểu diễn hình 7.15 thuật tốn mơ tả sau: Giả mã cho hàm tìm kiếm
(196)dành
cho
hội
đồng
nghiệm
thu
188 Con trỏ nhớ
while (here không trỏ tới nút chứa target here không trỏ tới nút cuối danh sách) {
Thực cho trỏ here trỏ tới nút danh sách. }
if (nút trỏ tới trỏ here chứa target)
return here;
else
return NULL;
Để di chuyển trỏ here tới nút kế tiếp, nút trỏ thành viên trỏ nút thời trỏ here Thành viên trỏ nút thời trỏ here cho biểu thức
here ->link
Di chuyển trỏ here tới nút danh sách:
here = here ->link;
Tổng hợp phần lại, có thuật tốn theo giả mã sau: Sơ mã nguồn cho hàm tìm kiếm:
here = head;
while (here ->data != target && here ->link != NULL) here = here ->link;
if (here ->data == target )
return here;
else
return NULL;
Chú ý biểu thức logic câu lệnh while Chúng ta kiểm tra để xem here không trỏ tới nút cuối việc kiểm tra xem biến thành viên here->link khác giá trị NULL hay không? Nếu kiểm tra mã nguồn hàm cho trường hợp đặc biệt (ở trường hợp danh sách rỗng), thấy có vấn đề Nếu danh sách rỗng, here NULL biểu thức sau khơng xác định được:
here ->data here ->link
Khi trỏ here NULL, khơng trỏ tới nút nào, nên khơng có thành viên tên data khơng có thành viên tên link Vì vậy, thực trường hợp đặc biệt danh sách rỗng Hàm định nghĩa hoàn thiện hình7.16
Khai báo hàm: struct Node {
3 int data; Node *link; };
6
7 typedef Node* NodePtr ;
9 // Dau vao: Con tro head tro toi dau danh sach lien ket.
10 // Dau ra: Tra ve tro tro toi nut dau tien ma chua gia tri target Neu khong co
(197)dành
cho
hội
đồng
nghiệm
thu
7.7 Lập trình với danh sách liên kết 189
Target is 6
2
6 head
1 here
3 NULL 2.
Not here
6 head
1
?
here
3 NULL 1.
2
6 head
1 here
3 NULL 3.
Not here
2
6 head
1
here
3 NULL 4.
Found
Hình 7.15: Tìm kiếm danh sách liên kết
11 // chua gia tri target ham tra ve NULL. 12 NodePtr search ( NodePtr head , int target );
Định nghĩa hàm:
1 NodePtr Search ( NodePtr head , int target ) {
(198)dành
cho
hội
đồng
nghiệm
thu
190 Con trỏ nhớ
4
5 if (here == NULL)
6 {
7 return NULL; Empty list case
8 }
9 else{
10 while (here ->data != target && 11 here ->link != NULL)
12 here = here ->link; 13
14 if (here ->data == target )
15 return here;
16 else
17 return NULL;
18 } 19 }
Hình 7.16: Xác định vị trí nút danh sách liên kết Con trỏ biến iterator
Biến iterator cấu trúc cho phép bạn duyệt toàn hạng mục liệu lưu cấu trúc liệu để bạn thực việc bạn muốn hạng mục Một iterator đối tượng lớp integrator đơn giản số mảng trỏ Nó khai báo sau:
Node_Type *iter;
for (iter = head; iter != NULL; iter = iter ->link)
Điều kiện head trỏ tới nút đầu danh sách liên kết link tên biến thành viên nút mà nút trỏ tới nút danh sách
Ví dụ, để xuất liệu tất nút danh sách liên kết thảo luận trên, sử dụng:
NodePtr iter; //ươTng đương ớvi: Node *iter;
for (iter = head; iter != NULL; iter = iter ->link) cout << (iter ->data );
Định nghĩa Node NodePtr 7.16 Chèn gỡ bỏ nút bên danh sách
Tiếp theo, ta thiết kế hàm để chèn nút vị trí cụ thể danh sách liên kết, ta muốn nút có thứ tự định, thứ tự số thứ tự chữ cái, ta đơn giản chèn nút vào vị trí bắt đầu hay kết thúc danh sách Do ta xây dựng hàm để chèn nút vào sau nút định danh sách Chúng ta giả sử số hàm khác một phần chương trình đặt xác trỏ có tên after_me trỏ tới nút trong danh sách liên kết Giờ ta muốn nút đặt sau nút trỏ tới after_me minh họa hình7.17 Cách làm tương tự cho nút với kiểu liệu nào, để cụ thể, ta sẽ sử dụng nút có kiểu Node phần trước Khai báo hàm sau:
//Đầu vào: after_me trỏ tới nút danh
(199)dành
cho
hội
đồng
nghiệm
thu
7.7 Lập trình với danh sách liên kết 191
//Đầu ra: Nút chứa the_number thêm vào
//sau trỏ after_me.
void insert ( NodePtr after_me , int the_number );
2
9 head
3 after_me
18 NULL
5 temp_ptr
Hình 7.17: Chèn phần tử vào danh sách liên kết
Một nút thiết lập theo cách hàm head_insert hình7.12 Sự khác hàm việc ta muốn chèn nút không vào đầu danh sách, mà sau nút được trỏ trỏ after_me Cách thức chèn mô tả hình7.17 thể C++ sau:
//Thêm liên kết từ nút tới danh sách: temp_ptr ->link = after_me ->link;
//Thêm liên kết từ danh sách tới nút mới: after_me ->link = temp_ptr ;
Thứ tự hai phép gán quan trọng, câu lệnh thứ hai thực trước after_me->link bị thay đổi trước gán cho temp_ptr->link , tượng nút sẽ xảy Người đọc kiểm tra lại điều thơng qua hình 7.17
Bản hồn thiện hàm insert đưa hình7.18 void Insert ( NodePtr after_me , int the_number )
2 {
3 NodePtr temp_ptr ; temp_ptr = new Node;
6 temp_ptr ->data = the_number ;
8 temp_ptr ->link = after_me ->link; after_me ->link = temp_ptr ;
10 }
Hình 7.18: Hàm thêm nút vào danh sách liên kết
(200)dành
cho
hội
đồng
nghiệm
thu
192 Con trỏ nhớ
before ->link = discard ->link;
Lệnh đủ để loại bỏ nút từ danh sách liên kết, ta khơng sử dụng nút hủy trả lại vùng nhớ mà sử dụng, thực việc với lời gọi delete sau:
delete discard ;
2
3 head
1
before
5 NULL
6
discard
2 head
1
before
5 NULL
6
discard
Recycled
3 delete discard;
Hình 7.19: Xóa nút danh sách liên kết
7.7.2 Danh sách liên kết lớp
Trong ví dụ trước, ta tạo danh sách liên kết việc sử dụng kiểu cấu trúc để tổ chức nội dung nút danh sách, tạo cấu trúc liệu tương tự sử dụng kiểu lớp thay cấu trúc Về mặt logic định nghĩa giống hệt ngoại trừ cú pháp sử dụng định nghĩa lớp