Như vậy sơ đồ giải thuật IPO của chương trình sử dụng một mảng hai chiều để lưu trữ số lượng đơn đặt hàng cho bốn chi nhánh trong ba tháng.. Chương trình cho phép người dùng lần lượt nhậ
Trang 3Mục lục
CHƯƠNG 1: MẢNG HAI CHIỀU 1
I Giới thiệu 1
II Khai báo Mảng hai chiều 2
1 Cú pháp khai báo và khởi tạo: 2
2 Một số ví dụ 2
III Gán giá trị cho Mảng hai chiều 3
1 Sử dụng câu lệnh gán 3
2 Sử dụng cin » 4
IV Truy xuất Mảng hai chiều 6
1 Truy xuất trực tiếp: * 6
2 Truy xuất tuần tự: 6
3 Một số ví dụ 6
V Truyền Mảng hai chiều cho hàm 8
□ Tóm tắt chương 9
□ Câu hỏi củng cố 9
□ Bài tập 10
Chương 2: BIẾN CON TRỎ 12
I Khái niệm con trỏ, địa chỉ trong bộ nhớ: 12
II Khai báo, khởi tạo biến con trỏ: 12
1 Cú pháp khai báo : 12
2 Khởi tạo giá trị: 13
2.1 Toán tử địa chỉ& 13
■ 2.2 Toán tử * 13
2.3 NULL: 14
2.4 Con trỏ kiểu void 14
III Các phép toán trên con trỏ : 15
1 Phép toán gán: 15
2 Phép toán tăng, giảm địa chỉ: 15
2.1 Phép toán+ ,-: 15
2.2 Phép-tơám++,—7 /777T77777777.7 .;; ị 16
A u.-â Đ^NG C0NG NGH$ 17
THUbUC
TWTfVTFN
Trang 45 Phép toán so sánh 18
IV Cấp phát bộ nhớ động 18
1 Cấp phát động và cấp phát tĩnh 18
2 Toán tử cấp phát bộ nhớ new 19
3 Toán tử thu hồi bộ nhớ delete 20
V Gon trỏ và Mảng 21
1 Cú pháp 21
2 Con trỏ và mảng một chiều 21
3 Con trỏ và mảng hai chiều 22
□ Tóm tắt chương 24
□ Câu hỏi củng cố 25
* □ Bài tập : 26
CHƯƠNG 3: ĐỆ QUY 28
I Định nghĩa đệ quy 28
1 Định nghĩa 28
2 Phân loại đệ quy 28
II Thiết kế hàm đệ quy 29
1 Hai bước giải toán đệ quy , 29
2 Cấu trúc hàm đệ quy 29
III Ưu điểm và nhược điểm của hàm đệ quy 31
IV Một số bài toán đệ quy 31
1 Tìm Ước số chung lớn nhất (UCLN) của hai số nguyên 31
2 Tính số hạng thứ n trong dãy Fibonaci 32
3 Bài toán cổ Tháp HảNộiị 32
□ Tóm tắt chương ; 33
□ Câu hỏi củng cố 34
□ Bài tập 34
CHƯƠNG 4: CHUỖI KÝ Tự 38
I Khái niệm chuỗi 38
1 Hằng chuỗi ' 38
2 Biến chuỗi 38»
II Khai báo biến chuỗi 38
Trang 5III Nhập, xuất chuỗi 39
1 Nhập chuỗi 39
1.1 Nhập chuỗi bang cin» 39
1.2 Nhập chuỗi bằng cin.get() 40
1.3 Phương thức cin.ignore(l) 42
1.4 Phương thức cin.get(c) * 42
1.5 Nhập chuỗi bằng cin.getline() 43
1.6 Xuất chuỗi: 43
IV Một số hàm xử lý chuỗi: 43
1 strcpy(s, t); 43
2 strcat(s, t); 44
3 strcmp(s, t); 44
4 strcmpi(s, t); 45
5 strupr(s); , 45
6 strlwr(s); 45
7 strlen(s); 45I V Con trỏ và chuỗi 48
□ Tóm tắt chương * 49
□ Câu hỏi củng cố 50
□ Bài tập 50
Chương 5: KIÊU DỮ LIỆU CẤU TRÚC 51
I Khái niệm cấu trúc 51
II Khai báo biển cấu trúc 51
1 Định nghĩa 51
2 Khai báo kiểu cấu trúc 52
3 Ví dụ: 52
III Truy nhập các thành phần kiểu cấu trúc 54
1 Đối với biến thường 54
2 Đối với biến con trỏ: 54
3 Đối với biến mảng: 55
4 Đối với cấu trúc lồng nhau 55
5 Phép toán gán cấu trúc 55
Trang 66 Các ví dụ minh hoạ , 57
IV Con trỏ với cấu trúc 59
1 Con trỏ và địa chỉ cấu trúc 59
2 Địa chỉ của các thành phần của cấu trúc 61
3 Đối của hàm là cấu trúc ' 62
4 Một số ví dụ: ; 62
□ Tóm tắt chương 67
□ Câu hỏi củng cố 68
□ Bài tập 68
CHƯƠNG 6: TẬP TIN 69
I Khái niệm về tập tin : 69
1 Khái niệm 69
2 Phân loại 69
2.1 Phân loại theo phương pháp truy cập 69
2.2 Phân loại theo bản chất dữ liệu tập tin 69
3 Bộ nhớ đệm (buffer) 70
II Các thao tác tên tập tin 70
1 Khai báo ' 70
2 Mở tập tin 71
3 Hàm kiểm tra lỗi mở tập tin: 72
4 Hàm kiểm tra kết thúc tập tin: 72
5 Đóng tập tin 72
III Truy xuất tập tin 73
1 Truy xuất dữ liệu trên tập tin nhị phân: 73
2 ’ Truy xuất trực tiếp trên tập tin nhị phân 74
3 Truy xuất dữ liệu trên tập tin văn bản: 77
4 Một số thao tác khác: 78
□ Tóm tắt chương 79
□ Bài tập 79
Trang 7CHƯƠNG 1: MẢNG HAI CHIỀU
Mục tiêu:
Sau khi học xong chương này Sinh viên có khả năng:
- Trình bày khái niệm về mảng dữ liệu nhiều chiều
- Tổ chức, truy xuất dữ liệu mảng hai chiều
- Truyền mảng đến hàm
I Giới thiệu •
Một mảng là biến lưu trữ một nhóm các phần tử có cùng kiểu dữ liệu Đa số các mảng được dùng trong các ứng dụng thương mại là mảng một chiều và mảng hai chiều Chúng ta có thể hình dung nếu mảng một chiều như là một dãy liên tục các biến trong bộ‘nhớ thì mảng hai chiều là một bảng các biến tổ chức theo dòng và cột Nói cách khác, mỗi phần tử trong mảng hai chiều là một mảng một chiều
Giả sử chúng ta có mảng hai chiều tên là “order” có bốn dòng và ba cột, mảng
sẽ được ký hiệu là A[4][3] Lưu ý ràng chỉ số dòng đứng trước, chỉ số cột đứng sau Trong ngôn ngữ c và C++, chỉ số dòng và cột sẽ được đánh số theo thứ tự 0, 1,2, Tổng quát hóa, nếu mảng có d dòng và c cột, thì chỉ sô dòng
sẽ được đánh số từ 0 đến d-1 và chỉ sổ cột từ 0 đến c-1 Tổng số phần tử của mảng A được tính bằng cách lấy số dòng d nhân với số cột c
MảngA[4][3]
Tình huống: Công Ty Mambo có bốn chi nhánh ở Hà Nội, Huế, Thành phố
Hô Chí Minh, cân Thơ Công ty muôn có một chương trình quản lý sô lượng đớn hàng ở mỗi chi nhánh trong ba tháng đầu năm Như vậy sơ đồ giải thuật IPO của chương trình sử dụng một mảng hai chiều để lưu trữ số lượng đơn đặt hàng cho bốn chi nhánh trong ba tháng Chương trình cho phép người dùng lần lượt nhập vào số lượng đơn hàng trong tháng của từng chi nhánh và in ra màn hình máy tính
Tài liêu giảng dav Kỹ Thuât Lâp Trình Trang 1
Trang 8Input: Processing: Output (screen):
Số lượng các đơn Mảng (4 dòng cho chi nhánh, 3 In ra mà hình sốhàng của 4 chi cột cho tháng) lượng các đơnnhánh trong 3
tháng
♦
Chỉ số của chi nhánh (từ 0->3)Chỉ số của tháng (từ 0->2)
hàng của 4 chi nhánh trong 3 tháng
ứng với chỉ số của chi nhánh và chỉ số của thángKết thúc lặp 2
Kết thúc lặp 1
Hình 1: Sơ đồ IPO của chương trình quản lý số lượng đơn hàng của Công
ty Mambo
II Khai báo Mảng hai chiều
1 Cú pháp khai báo và khởi tạo:
Kiểu_dữ_liệu Tên_mảng[Số_dòng][Sổ_cột]= {{giá_trị_l}, {gìá_trị_2}, , { giatrin}};
Trang 9int order[4][3]={{0, 0,0} , {0,0,0} , {0,0,0} , {0,0,0}};
Ví dụ 2: khai báo mảng ký tự có tên là grades gồm 3 dòng và 2 cột
char grades[3][2] = {{‘A’, ‘B’} , {‘C’ , ‘D’} , {‘E , ‘F’}};
Ví dụ 3: khai báo mảng số thực có tên là prices gồm 6 dòng và 5 cột; phần tử đầu tiên prices[0][0] được khởi tạo là 2.0; các phần tử còn lại có giá trị khởi tạo là 0.0
sẽ lưu giá trị của ô nhớ gốc trước đó làm giá trị cho phần tử mảng Điều này
có khi sẽ làm chương trình bạn cho kết quả không đúng Vì vậy nên khởi tạo giá trị cho các phần tử của mảng ngay khi khai báo chúng
III Gán giá trị cho Mảng hai chiều
for (int row = 0; row < 4; row++)
Trang 10const double INCREASE = 1.15;
Tài liệu giảng dạy Kỹ Thuật Lập Trình Trang 4
Trang 11Ví dụ 2:
for (int region = 0; region < 4; region ++)
for (int month = 0; month < 3; month ++)
cout« "Number of orders for Region "
« region + 1 « ", Month "
« month + 1 «cin » orders[region] [month];
} //end for
//end for Đoạn lệnh cho phép người dùng nhập vào số lượng đon hàng của từng chi nhánh theo từng tháng Vòng lặp For ngoài dùng để duyệt 4 hàng của mảng oders tưong tứng với 4 chi nhánh của Công ty và vòng lặp For trong dùng
để duyệt mảng 3 cột của mảng orders tưong ứng tháng 1, 2, 3 của từng chi nhánh
Trang 12IV.Truy xuất Mảng hai chiều
Có nhiều cách truy xuất các phần tử trên mảng hai chiều, tùy theo yêu cầu của bài toán mà ta chọn cách truy xuất phù hợp
1 Truy xuất trực tiếp:
Truy xuất trực đến bất kỳ phần tử trong mảng
Ví dụ câu lệnh cout « A[2][3] cho phép ta truy xuất đến phần tử [2,3] của mảng A để lấy giá trị và in ra màn hình
2 Truy xuất tuần tự:
Truy xuất tuần tự từng phần tử trong mảng theo các hướng duyệt mảng khác nhau như:
o Truy xuất từng dòng theo từ trái sang phải và hướng từ trên xuống dưới
Ta dùng hai vòng lặp For lồng nhau và thay đổi các biến điều khiển vòng lặp
để có các hướng duyệt đúng theo yêu cầu bài toán
for(int i=0;i<d;i++){
for(int j=O;j<c;j++){
Trang 13cout« grades [row] [column] « “ row ++;
} //end while column ++;
Ví dụ 2: Sử dụng vòng lặp for để duyệt mảng theo từng dòng
for (int region = 0; region < 4; region += 1) {
for (int month = 0; month < 3; month += 1)
cout « orders [region] [month] « "
}//end for cout« endl;
cout «endl;
Trang 14} while (row < 6);
In ra màn hình 6 dòng, 5 cột của mảng prices bằng cách duyệt mảng
theo từng dòng
V Truyền Mảng hai chiều cho hàm
Có thể sử dụng mảng hai chiều như là một tham số cho hàm Chương trình sau cho phép nhập và xuất mảng orders Thao tác nhập và xuất mảng được viết thành hai hàm con với tham số truyền vào là mảng orders
#include <iostream>
using namespace std;
ttdefine MAXR 30 //số dòng lớn nhất của màng là 20
//Khai bao prototype
for (int i = 0; i < nbranch; Í++)
for (int j =0; j < nmonth; j++)
Trang 15//end for
}
J Nếu duyệt phần tử mảng theo cách ưu tiên duyệt trên dòng rồi mới đến cột thì vòng lặp bên ngoài sẽ quản lý chỉ số dòng,vòng lặp bên trong sẽ quản lý chỉ số cột Và ngược lại
• S Mảng có thể dùng để truyền đối số cho hàm Có thể không khai báo số dòng mà chỉ cần khai báo số cột khi truyền mảng cho hàm
Trang 1613 for (int location = 0; location < 2; location += 1)
14 for (int month = 0; month < 6; month += 1)
15 company Sales += company [location] [month];
a Hãy điền nội dung cho các ghi chú từ [1] đến [7],
b Hãy chuyển đổi vòng lặp for trong đoạn code câu trên thành vòng lặp while
c Nếu vòng lặp for ở dòng 14 trong đoạn code ở trên được thay bằng for (int month = 1; month < 6; month += 1) thì câư lệnh gán trong vòng lặp for thay đổi như thế nào?
d Hãy sửa đoạn chương trình trên để hiển thị kết quả hiển thị là tổng
’ theo dòng hoặc tổng theo cột của mảng hai chiều
e Hãy nhập và chạy đoạn chương trình trên máy tính
❖ Bài tập
1 Công Ty Mambo có bốn chi nhánh ở Hà Nội, Huế, Thành phố Hồ Chí Minh, cân Thơ Công ty muôn có một chương trình quản lý sô lượng đơn hàng ở mỗi chi nhánh trong năm Viết chương trình cho phép người dùng thực hiện các thao tác sau:
a) Nhập, xuất số lượng đơn hàng của từng chi nhánh trong từng tháng b) Tính tổng số đơn hàng của chi nhánh 2 trong quý đầu tiên
Tài liệu giảng dạy Kỹ Thuật Lập Trình Trang 10
Trang 17c) Tính tổng đơn hàng của từng chi nhánh trong quý đầu tiên
d) Tính tổng số đơn hàng trong tháng 1
e) Tính tổng số đơn hàng trong từng tháng
f) Tìm đơn hàng có số lượng lớn nhất trong từng chi nhánh
g) Tìm đơn hàng có số lượng nhỏ nhất trong từng tháng
h) Tìm chi nhánh có tổng số đơn hàng lớn nhất
i) Tìm tháng có số lượng đơn hàng lớn nhất
j) Tính số đơn hàng có số lượng lớn hơn 10
2 Viết hàm nhập, xuất dữ liệu cho ma trận các số nguyên Với các số nằm trong khoảng từ 0 đển 10 Nếu người dùng nhập sai thì yêu cầu nhập lại cho đến khi nhập đúng thì mới được nhập phần tử tiếp theo
3 Nhập giá trị hai ma trận A, B có kích thước bằng nhau In a giá trị ma trận C=A+B
4 Cho ma trận A có giá trị là các số nguyên Ma trận c được tạo ra từ A với các phần tử âm trong ma trận A sẽ bằng 0 trong ma trận c
5 Nhập ma trận A có kích thước mxn Tạo ma trận vuông từ A và tính tổng các phần tử trên đường chéo chính, phụ, tam giác trên/ dưới đường chéo chính/phụ
6 Viết hàm đếm số phần tử có giá trị bằng X trong ma trận, với X do người dùng nhập vào
7 Tìm phần tử lớn nhất/nhỏ nhất trong ma trận
8 Tìm phần tử lớn nhất/ nhỏ nhất trên các dòng/cột của ma trận
9 Tính Trung bình cộng các phần tử theo dòng / cột
10 Viết hàm đảo vị trí dòng, cột trong ma trận
11 Viết hàm sắp xếp các phần tử trong mảng theo thứ tự tăng/giảm dần
12 Viết hàm sắp xếp các phần tử trên mỗi dòng/cột theo thứ tự tăng/giảm dần
Hướng dẫn: Ma trận vuông lầ một Mảng hai chiều có số dòng bàng với số
cột Nhóm vị trí các phần tử được xác định dựa trên đường chéo chính và đường chéo phụ
Trang 18Chương 2: BIÉN CON TRỎ
Mục tiêu:
Sau khi học xong chương này Sinh viên có khả năng:
Trinh bày khái niệm về con trỏ, địa chỉ bộ nhớ, nội dung trbng bộ nhớ Thực hiện cấp phát bộ nhớ động
- Sử dụng tham chiêu cho hàm
- So sánh con trỏ, mảng 1 chiều và chuỗi
I Khái niệm con trỏ, địa chỉ trong bộ nhở:
Con trỏ là một biến mà nội dung của nó chứa địa chỉ của một đối tượng khác Đổi tượng ở đây có thể là một biến, mảng hoặc một hàm nhưng khong6phai3
là một hằng số
Nếu pa là con trỏ chứa địa chỉ của biến a ta gọi pa trỏ tới a và a được trỏ bởi
pa Thông qua con trỏ ta có thể làm việc được với nội dung của những ô nhớ
mà pa trỏ đến
Con trỏ sẽ có cùng kiểu dữ liệu với biến mà nó trỏ đến và ta có thể thực hiện
Hình: Minh họa biến pa trỏ đến biến a
II Khai báo, khởi tạo biến con trỏ:
1 Cú pháp khai báo :
<kiểu dữ liệu > < * tên biến> ;
Địa chỉ của một biến là địa chỉ byte nhớ đầu tiên của biến đó Vì vậy để lấy được nội dung của biến, con trỏ phải biết được so byte của biến, tức kiểu của biến mà con trỏ sẽ trỏ tới Kiểu này cũng được gọi là kiểu của con trỏ Như vậy khai báo biến con trỏ cũng giống như khai báo một biến thựờng ngoại trừ cần thêm dấu * trước tên biến (hoặc sau tên kiểu) Ví dụ:
- Khai báo biến p là biến con trỏ trỏ đến kiểu dữ liệu nguyên:
jnt p* ;
- Khai báo hai biến p, q là biến con trỏ trỏ đến kiểu dữ liệu thực:
Tài liệu giảng dạy Kỹ Thuật Lập Trình Trang 12
Trang 19float *q, *r ;
- Khai báo biển con trỏ nhưng chưa xác định trỏ đen một kiểu nào:
void p;*
Con trỏ kiểu không có kiểu : là con trỏ đặt biệt Nó có thể nhận địa chỉ của
một đối tượng bất kỳ Con trỏ không kiểu được khai báo với kiểu dữ liệu là
void như sau :
void *pv ;
Một số lưu ý:
+ Trong câu lệnh khai báo biến con trỏ, phép toán * có thể đặt bất kỳ vị trí nào giữa Kiểu dữ liệu và Tên biến Nghĩa là chúng ta có thể viết int
*p hoặc int * p hoặc int * p
+ Trong câu lệnh khai báo có dạng int* p, q hay int *p,q, thì chỉ có p là biến con trỏ còn q là một biết đcm kiểu số nguyên Như vậy để khai báo hai biển p, q cùng là con trỏ ta phải viết int *p, *q
+ Tóm lại để tránh nhầm lẫn ta nên viết câu lệnh khai báo theo kiểu tương
tự int * hoặc int *p,*q
2 Khởi tạo giá trị:
2.1 Toán tử địa chỉ &
Trong C/C++, toán tử & là toán tử một ngôi trả về địa chỉ của toán hạng sau
Trang 202.4 Con trỏ kiểu void
Đối với con trỏ kiểu void, ta có thể gán cho pv trỏ tới một biến có kiểu dữ liệu bất kỳ :
void *pv ;inti=T,
*pi;
float f=0.5 ; *pf ;pi=&i ;
pi = pv ; hay pf = pv ; // SAI
Tài liệu giảng dạy Kỹ Thuật Lập Trình Trang 14
Trang 21*+ & và là 2 phép toán ngược nhau Cụ thể nếu p = &x thì X = p Từ đó nếu p trỏ đến X thì bất kỳ nơi nào xuất hiện X đều có thể thay được bởi
Gán con trỏ với địa chỉ một biến: p = &x ;
Gán con trỏ với con trỏ khác: p = q ; (sau phép toán gán này p, q chứa cùng một địa chỉ, cùng trỏ đến một nơi)
//i= 10*10 + 2*10+ 1// 121
2 Phép toán tăng, giảm địa chỉ:
2 1 Phép toán + , -:
p ± n: con trỏ trỏ đển thành phần thứ n sau (trước) p
Một đơn vị tăng giảm của con trỏ bằng kích thước của biển được trỏ Ví dụ giả sử p là con trỏ nguyên (2 byte) đang trỏ đến địa chỉ 200 thì p+1 là con trỏ trỏ đến địa chỉ 202 Tương tự, p + 5 là con trỏ trỏ đến địa chỉ 210 p - 3 chứa địa chỉ 194
Tài liêu PĨảne dav Kỹ Thuât Lân Trình Trang 15
Trang 22194 196 198 200
A 202 204 206 208 210 212
Ví dụ:
T p
for (int i=0; i<l 00; i++)
cout« *(p+i) ; // in toàn bộ mảng a
p) ; // tăng (trước) giá trị nơi p trỏ: tăng a[0] thành 4
* (p++); // lấy giá trị nơi p trỏ (3) và tăng trỏ p (tăng sau), p ->a[l ]
* (++p); // tăng trỏ p (tăng trước), p a[l] và lấy giá trị nơi p trô (7)Chứ ý:
• Phân biệt p+1 và p++ (hoặc ++p):
t
Tài liệu giảng dạy Kỹ Thuật Lập Trình Trang 16
Trang 23• p++ là con trỏ p nhưng trỏ đến phần tử khác p++ trỏ đến phần tử đứng ngay sau phần tử p trỏ đến ban đầu.
• Phân biệt (p++) và (++p):* *
Các phép toán tự tăng giảm cũng là một ngôi, mức ưu tiên của chúng là cao hon các phép toán hai ngôi khác và cao hon phép lấy giá trị (*) Cụ thể:
*(p++)
*(++p)
' Cũng giống các biến nguyên việc kết hợp các phép toán này với nhau rất
dễ gây nhầm lẫn, do vậy cần sử dụng cặp dấu ngoặc để qui định trình tự tính toán
Ví dụ:
int i, j; // khai báo 2 biến nguyên i, j
int *p, *q ; // khai báo 2 con trỏ nguyên p, q
p = &i; // cho p trỏ tới i
q = &j; // cho q trỏ tới j
cout« &i; // hỏi địa chỉ biến i
cout« q ; //hỏi địa chỉ biến j (thông qua q)
&i tương đương với p, &j tương đương với q
4 Hiệu của 2 con trỏ
Phép toán này chỉ thực hiện được khi p và q là 2 con trỏ cùng trỏ đen các phần
tử của một dãy dữ liệu nào đó trong bộịjỊhộ\(ví dụ PUơg.ttế)dêỉi\Ctìlảng dữ liệu) Khi đó hiệu p - q là số phần tử giữa p và q q không phải làhiệu của 2 địa chỉ mà là sô thành phân giữa p^và-q);— -X"-—
Tài liêu giảng dav Kỹ Thuật Lập Trình Trậng 17
Trang 24Ví dụ: giả sử p và q là 2 con trỏ nguyên, p có địa chỉ 200 và q có địa chỉ 208 Khi đó p - q = -4 và q - p = 4 (4 là sô thành phân nguyên từ địa chỉ 200 đên 208).
Lưu ý: Không thực hiện phép cộng giữa hai con trỏ
< *p ; // in toàn bộ mảng a
(a[3]) của
IV Cấp phát bộ nhớ động
1 Cấp phát động và cấp phát tĩnh
Cấp phát tĩnh (static memory allocation): khi khai báo biến, màng, cấu trúc
bắt buộc phải khai báo trước cần bao nhiêu dung lượng bộ nhớ để lưu trữ Trong suốt quá trình thực thi chương trình, khối lượng bộ nhờ này sẽ bị chương trình chiếm dụng và không thể thay đổi kích thước Điều này dẫn đến việc hao tốn bộ nhớ máy tính khi chương trình sử dụng hết hoặc chương trình không thể tiếp tục chạy trong trường hợp dung lượng bộ nhớ dành cho chương trình đã bị dùng hết
Cấp phát động (dynamic memory allocation): khắc phục được hạn chế của
việc cấp phát tĩnh, khi dùng kỹ thuật câp phát động lập trình viên không cân phải khai báo trước kích thước biến mảng, cấu trúc Khi chương trình cần bao nhiêu sẽ cấp phát bấy nhiêu và có thể giải phóng bộ nhớ khi không cần dùng đến nữa
Tài liệu giảng dạy Kỹ Thuật Lập Trình Trang 18
Trang 25Vùng cấp phật tình (kích thước cố định)
p = new <kiểu>[n] ;
Ví dụ:
int *p ;
p = new int ; // cấp phát vùng nhớ chứa được 1 số nguyên
p = new float [100] ; // cấp phát vùng nhớ chứa được 100 số thựcKhi gặp toán tử new, chương trình sẽ tìm trong bộ nhớ một lượng ô nhớ còn trống và liên tục với số lượng đủ theo yêu cầu và cho p trỏ đến địa chỉ (byte đầu tiên) của vùng nhớ này Nếu không có vùng nhớ với số lượng như vậy thì việc cấp phát là thất bại và p = NULL (NULL là một địa chỉ rỗng, không xác định) Do vậy ta có thể kiểm tra việc cấp phát có thành công hay không thông qua kiểm tra con trỏ p bằng hay khác NULL
Ví dụ:
float *p ;
int n;
cout« "Số lượng cần cấp phát = cin » n;
Tài liêu giảng dav Kỹ Thuật Lập Trình Trang 19
Trang 26Ghi chú: lệnh exit(O) cho phép thoát khỏi chương trình, đế sử dụng lệnh này cần khai báo file tiêu đề <stdlib.h>.
3 Toán tử thu hồi bộ nhớ delete
Để giải phóng bộ nhớ đã cấp phát cho một biến (khi không cần sử dụng nữa)
ta sử dụng câu lệnh delete
delete p ; // p là con trỏ được sử dụng trong new
và để giải phóng toàn bộ mảng được cấp pháp thông qua con trỏ p ta dùng câu lệnh:
}
for (p=head; p<head+n; p++) // nhập dãy
{
cout « "So thu " «' p-head+1 « ; cin » *p ;
for (p=head; p<head+n-1; p++) // sắp xếp
for (q=p+l; q<head+n; q++)
Tài liệu giảng dạy Kỹ Thuật Lập Trình Trang 20
Trang 27if (*q < *p) {tarn = *p; *p = *q; *q = tam; } for (p=head; p<head+n; p++) cout« *p ;
return 0;
}
// đổi chỗ// in kết quả
V Con trỏ và Mảng
1 Cú pháp
Con trỏ trỏ đến mảng một chiều:
int a[]; //khai báo mảng một chiều a
int *p; //khai báo con trỏ p
p=a; // hoặc p=&a[0] : p đến phần tử a[0]
Con trỏ trỏ đến mảng hai chiều:
int a[][MAX]; //khai báo mảng một chiều a
int *p; //khai báo con trỏ p
p=&a[0] [0]; // hoặc p=&a[0] : p trỏ đến phần tử a[0] hoặc p=a
Việc cho con trỏ trỏ đến mảng cũng tương tự trỏ đến các biến khác, tức gán địa chỉ của mảng (chính là tên mảng) cho con trỏ
2 Con trỏ và mảng một chiều
Chú ý rằng địa chỉ của mảng cũng là địa chỉ của thành phần thứ 0 nên a+i sẽ là địa chỉ thành phần thứ i của mảng
Tương tự, nếu p trỏ đến mảng a thì p+i là địa chỉ thành phần thứ i của mảng a
và do đó *(p+i) = a[i] = *(a+i)
Cụ thể nếu i=2 thì *(p+2) = a[2] =*(a+2) = giá trị phần tử thứ ba của mảng a
Tài liêu eiảns dav Kỹ Thuât Lân Trình Trang 21
Trang 28là địa chỉ đầu mảng cộng thêm số phần tử trong a và trừ 1 (tức a+4 trong ví dụ trên).
3 Con trỏ và mảng hai chiều
Thực tế, bộ nhớ máy tính lưu trữ các phần tử của mảng hai chiều thành một dãy các ô nhớ liên tiếp giống như mảng một chiều Vì vậy, việc sử dụng con trỏ để điều khiển mảng hai chiều có m dòng và n cột tương tự như với mảng một chiều có mxn phần tử
Tài liệu giảng dạy Kỹ Thuật Lập Trình Trang 22
Trang 29Cho p trỏ đến mảng hai chiều a[3][3].Như vậy:
Như vậy để truy cập đến phần tử thứ a[i][j] ta dùng phép toán *(p+ i*m + j),
Ví dụ: ta viết lại ứng dụng của Công ty Mambo trong chương I nhưng sử dụng con trỏ p để thao tác trên mảng mảng thay vì là thao tác trực tiếp trên mảng orders Như vậy giá trị phần tử orders [region] [month] sẽ được thay bằng *(
for (int region = 0; region < 4; region += 1)
for (int month = 0; month < 3; month += 1)
cout« "Number of orders for Region "
« region + 1 « ", Month "
« month + 1 «":
Trang 30cin » *(p+region*3+month);
cout« "Region " « region + 1« " « endl;
for (int month = 0; month < 3; month += 1)
cout« " Month " « month + 1
« H It.
cout« *(p+region*3+month) « endl;
} //end for} //end for
system("pause");
return 0;
}
❖ Tóm tắt chương
J Biến con trỏ lấy địa chỉ của biến mà nó trỏ tới làm giá trị của nó
J Khai báo và khởi tạo biến con trỏ:
float a[5];
float *p=a;
inta[3][4J;
int *p = &a[0][0];
J Các phép toán trên con trỏ:
J Sử dụng con trỏ p để truy xuất mảng một chiều như sau:
Tài liệu giảng dạy Kỹ Thuật Lập Trình Trang 24
Trang 321 Hãy khai báo biến kí tự ch và con trỏ kiểu kí tự pc trỏ vào biến ch Viết
ra các cách gán giá trị ‘ A’ cho biến ch
2 Cho mảng nguyên cost Viết ra các cách gán giá trị 100 cho phần tử thứ
3 của mảng
3 Cho p, q là các con trỏ cùng trỏ đến kí tự c Đặt p = 'q + 1 Có thể khẳng định: q = p - 1 ?
6 Chọn câu đúng nhất trong các câu sau:
a Địa chỉ của một biến là số thứ tự của byte đầu tiên máy dành cho biến đó
b Địa chỉ của một biến là một số nguyên
c Số học địa chỉ là các phép toán làm việc trên các số nguyên biểu diễn địa chỉ của biến
d a và b đúng
Tài liệu giảng dạy Kỹ Thuật Lập Trình Trang 26
Trang 337 Chọn câu sai trong các câu sau:
a Các con trỏ có thể phân biệt nhau bởi kiếu của biến mà nó trỏ đến
b Hai con trỏ trỏ đến các kiểu khác nhau sẽ có kích thước khác nhau
c Một con trỏ kiểu void có thể được gán bởi con trỏ có kiểu bất Kỳ (cần ép kiểu)
d Hai con trỏ cùng trỏ đến kiểu cấu trúc có thể gán cho nhau
8 Cho con trỏ p trỏ đến biến X kiểu float Có thể khẳng định ?
a p là một biến và p cũng là một biến*
b p là một biến và p là một giá trị hằng*
c Để sử dụng được p cần phải khai báo float p; và gán p = x;* *
d Cũng có thể khai báo void p; và gán (float)p = &x;*
9 Cho khai báo float X, y, z, px, py; và các lệnh px = &x; py = &y; Có thể khẳng định ?
15 Làm lại các bài tập ở chương I dùng kỹ thuật con trỏ
Tài lipii ơiánơ Hav Kv Thuât Lân Trình Tran? 27
Trang 34CHƯƠNG 3: ĐỆ QUY
Mục tiêu:
Sau khi học xong chương này Sinh viên có khả năng:
Trình bày khái niệm về đệ qui
Trình bày các ưu nhược điểm của hàm đệ qui
Viết một sổ hàm đệ qui đơn giàn
I Định nghĩa đệ quỵ
1 Định nghĩa
Đệ quy là kỹ thuật tìm lời giải của một bài toán bằng cách tìm lời giải của bài toán nhỏ hơn Đệ quy là cách giải rất hữu dụng trong việc tìm lời giải của những bài toán phức tạp
Ngoài ra, hai điều kiện quan trọng để có thể giải bài toán bằng đệ quy là bài toán tồn tại bước đệ quy và phải có điều kiện dừng Trong toán học, bài toán tính giai thừa của một số nguyên không âm:
Tính S(n) = 1 T 2 + 3 + + (n- 1) + n
Ta nhận thấy: 1 + 2 + 3 + + (n=- 1) = S(n - 1) S(n) = S(n - 1) + n
Hơn nữa, S(0) = 0
Vậy, bài toán tồn tại bước đệ quy và có điều kiện dừng
Một hàm được gọi là đệ quy nếu bên trong thân của hàm đó có lời gọi hàm lại chính nó một cách trực tiếp hay gián tiếp
Khi thực hiện một hàm đệ qui, hàm sẽ phải chạy rất nhiều làn, trong mỗi lần chạy chương trình sẽ tạo nên một tập biến cục bộ mới trên ngăn xếp (các đối, các biến riêng khai báo trong hàm) độc lập với lần chạy trước đó, từ đó dễ gây tràn ngăn xêp Vì vậy đôi với những bài toán có thê giải được'băng phương pháp lặp thì không nên dùng đệ qui
2 Phân loại đệ quy
Có bốn loại đệ quy:
- Đệ quy tuyến tính: Trong thân hàm có duy nhất một lời gọi
hàm gọi lại chính nó một cách tường minh
- Đệ quy nhị phân: Trong thân hàm có hai lời gọi hàm gọi lại
chính nó một cách tường minh
Tài liệu giảng dạy Kỹ Thuật Lập Trình Trang 28
Trang 35■ - Đệ quy hổ tương: Trong thân hàm này có lời gọi hàm tới hàm
kia và bên trong thân hàm kia có lời gọi hàm tới hàm này
- Đệ quy phi tuyến: Trong thân hàm có lời gọi hàm lại chính nó được đặt bên trong thân vòng lặp
II Thiết kế hàm đệ quy
1 Hai bước giải toán đệ quy
Bước 1 - Phân tích: Phân tích bài toán thành bài toán đồng dạng nhưng đơn
giản hơn và dừng lại ở bài toán đồng dạng đơn giản nhất có thể xác định ngay kểt quả
Bước 2 - Thế ngược: Xác định kết quả bài toán đồng dạng từ đơn giản đến phức tạp để có kết quả cuối cùng
2 Cấu trúc hàm đệ quy
Một hàm thông thường gồm 2 phần sau:
<Kiêu_trả_về> <Tên hàm>(<Tham số>)
{
if (<Điều kiện dừng>)
{
• • • return <Giá trị>;
Để tính n! ta có thể dùng phương pháp lặp như sau:
Trang 36Mặt khác, n! giai thừa cũng được tính thông qua (n-1)! bởi công thức truy hồi n! = 1 nếu n = 0
n! = (n-l)!n nếun>0
do đó ta có thể xây dựng hàm đệ qui tính n! như sau:
double factor (int n)
if (n==0) return 1; '//điều kiện dừng
}
else return factor (n-l)* ; // bước đệ quy
Chương trình tính giai thừa sử dụng giải thuật đệ quy sẽ được viết như sau:
Ví dụ giá trị truyền vào hàm giai thua qua biến n = 5 Thứ tự gọi thực hiện hàm giaithua:
#include <iostream>
using namespace std;
double giaithua(int n)
if (n=0) return 1; //điều kiện dừng
else return gt(n-l)*n; // bước đệ quy
Trang 37III Ưu điểm và nhược điểm của hàm đệ quy
Từ ví dụ trên ta thấy hàm đệ qui có đặc điểm:
• Ưu điểm:
- Rõ ràng, dễ hiểu, nêu rõ bản chất vấn đề
- Tiết kiệm thời gian thực hiện mã nguồn
- Một số bài toán rất khó giải nếu không dùng đệ qui
• Khuyết điểm:
- Tốn nhiều bộ nhớ, thời gian thực thi lâu
- Một số tính toán có thể bị lặp lại nhiều lần
Chỉ một số bài toán có lời giải đệ quy Tuy nhiên, đệ qui là cách viết rất gọn,
dễ viết và đọc chương trình, mặt khác có nhiều bài toán h‘ầu như tìm một thuật toán lặp cho nó là rất khó trong khi viết theo thuật toán đệ qui thì lại rất dễ dàng ví dụ như bài toán Tháp Hà Nội sẽ được giới thiệu ở phần sau
IV Một số bài toán đệ quy
1 Tìm Ước số chung lớn nhất (UCLN) của hai số nguyên
Tìm ƯCLN của 2 số a, b Bài toán có thể được định nghĩa dưới dạng đệ qui như sau:
- nếu a = b thì UCLN = a
- nếu a > b thì UCLN(a, b) = ƯCLN(a-b, b)
- nếu a < b thì UCLN(a, b) = UCLN(a, b-a)
Từ đó ta có chương trình đệ qui để tỉnh UCLN của a và b như sau:
int UCLN(int a, int b) // qui uoc a b > 0
Wxr Th 11 of lân Trình Trnnơ 31
Trang 38if (a < b) ƯCLN(a, b-a);
if (a == b) return a;
if (a > b) UCLN(a-b, b);
}
2 Tính sô hạng thứ n trong dãy Fibonaci
Tính sổ hạng thứ n của dãy Fibonaci là dãy f(n) được định nghĩa:
3 Bài toán cổ Tháp Hà Nội
Chuyển tháp là bài toán cổ nổi tiếng, nội dung như sau: Cho một tháp n tầng, đang xếp tại vị trí 1 Yêu cầu bài toán là hãy chuyển toàn bộ tháp, sang vị trí 2 (cho phép sử dụng vị trí trung gian 3) theo các điêu kiện sau đây:,
+ Mỗi lần chỉ được chuyển một tầng trên cùng của tháp
+ Tại bất kỳ thời điểm tại cả 3 vị trí các tầng tháp lớn hon phải năm dưới các tầng tháp nhỏ hon
Bài toán chuyển tháp được minh hoạ bởi hình vẽ dưới đây:
trước khi chuyển sau khi chuyển
Tài liệu giảng dạy Kỹ Thuật Lập Trình Trang 32
Trang 39Bài toán có thể được đặt ra tổng quát hơn như sau: chuyển tháp từ vị trí source đến vị trí dest, trong đó source, dest là các tham số có thể lấy giá trị là 1, 2, 3 thể hiện cho 3 vị trí Đối với 2 vị trí đi và đến, dễ thấy vị trí trung gian tmp còn lại sẽ là vị trí (6 - source - dest) (vi source+dest+temp = 1+2+3 = 6) Từ
đó để chuyển tháp từ vị trí source đen vị trí dest, ta có thể xây dựng một cách chuyển đệ qui như sau:
+ chuyển 1 tầng từ source sang tmp,
+ chuyển n-1 tầng còn lại từ source sang dest,
+ chuyển trả tầng tại vị trí temp về lại vị trí dest
Hiển nhiên nếu số tầng là một thì ta chỉ phải thực hiện một phép chuyển từ
source sang dest.
Mỗi lần chuyển 1 tầng từ vị trí i đến j ta kí hiệu i j Chương trình sẽ nhập vào input là số tầng và in ra các bước chuyển theo kí hiệu trên
Từ đó ta có thể xây dựng chương trình chuyển tháp Hà Nội sử dụng hàm đệ qui sau đây:
void move(int n, int source, int dest)
// n: sổ tầng ; di,den:vị trí đi, đến
if (n=l) cout« source « "==>" « dest « endl;
else {
// 1 tầng từ source qua trung gian tmp
cout« source « "==>" « 6- source - dest « endl;
// n-1 tầng từ source qua dest
move (n-1, source, dest);
Trang 40J Hai điều kiện quan trọng để có thể giải bài toán bằng đệ quy là bài toán tồn tại bước đệ quy và phải có điều kiện dừng
J Một giải thuật đệ quy phải có ít nhất một điều kiện dừng ■
J Èai bước giải một bài toán đệ quy là Bước phân tích và Bước thê
ngược
J Chỉ nên sử dụng đệ quy khi'không thể giải quyết bài toán bằng vòng lặp
❖ Câu hỏi củng cố
1 Các phát biểu sau đây là đúng hay sai:
a Điều kiện để có thể giải bài toán bằng đệ quy là phải có bước đệ quy
b Các bài toán giải bằng đệ quy luôn luôn có thể chuyển sang bài toán dạng lặp
c Trong đệ quy chỉ có duy nhất một điều kiện dừng
d Chương trình sử dụng giải thuật đệ quy thì tốn bộ nhớ máy tính
và thời gian thực thi lâu
2 Cho hàm đệ quy sau:
int mystery(int number)
1 Tìm ƯSCLN của hai số nguyên
/* Tinh so ước số chung lớn nhất của hai số a,b */