Chương 1 đã trình bày tổng quan về các phương pháp lập trình hiện nay. Nội dung tập trung vào ba phương pháp lập trình có liên quan trực tiếp đến ngôn ngữ lập trình C++: • Lập trình tuyến tính • Lập trình hướng cấu trúc • Lập trình hướng đối tượng. C++ là ngôn ngữ lập trình được mở rộng từ ngôn ngữ lập trình cấu trúc C. Do đó, C++ vừa có những đặc trưng của lập trình cấu trúc, vừa có những đặc trưng của lập trình hướng đối tượng.com https://fb.com/tailieudientucntt Chương 2: Con trỏ và mảng CHƯƠNG 2 CON TRỎ VÀ MẢNG Nội dung của chương này tập trung trình bày các vấn đề cơ bản liên quan đến các thao tác trên kiểu dữ liệu con trỏ và mảng trong C++: • Khái niệm con trỏ, cách khai báo và sử dụng con trỏ.
• Mối quan hệ giữa con trỏ và mảng • Con trỏ hàm • Cấp phát bộ nhớ cho con trỏ 2.1 KHÁI NIỆM CON TRỎ 2.1 Khai báo con trỏ Con trỏ là một biến đặc biệt chứa địa chỉ của một biến khác. Con trỏ có cùng kiểu dữ liệu với kiểu dữ liệu của biến mà nó trỏ tới. Cú pháp khai báo một con trỏ như sau: <Kiểu dữ liệu> *<Tên con trỏ>; Trong đó: • Kiểu dữ liệu: Có thể là các kiểu dữ liệu cơ bản của C++, hoặc là kiểu dữ liệu có cấu trúc, hoặc là kiểu đối tượng do người dùng tự định nghĩa. • Tên con trỏ: Tuân theo qui tắc đặt tên biến của C++: - Chỉ được bắt đầu bằng một kí tự (chữ), hoặc dấu gạch dưới “_”.
- Bắt đầu từ kí tự thứ hai, có thể có kiểu kí tự số. - Không có dấu trống (space bar) trong tên biến. - Có phân biệt chữ hoa và chữ thường. - Không giới hạn độ dài tên biến.
Ví dụ, để khai báo một biến con trỏ có kiểu là int và tên là pointerInt, ta viết như sau: int *pointerInt; Lưu ý • Có thể viết dấu con trỏ “*” ngay sau kiểu dữ liệu, nghĩa là hai cách khai báo sau là tương đương: int *pointerInt; int* pointerInt; • Các cách khai báo con trỏ như sau là sai cú pháp: *int pointerInt; // Khai báo sai con trỏ int pointerInt*; // Khai báo sai con trỏ 2.2 Sử dụng con trỏ Con trỏ được sử dụng theo hai cách: 11 CuuDuongThanCong.com https://fb.com/tailieudientucntt Chương 2: Con trỏ và mảng • Dùng con trỏ để lưu địa chỉ của biến để thao tác • Lấy giá trị của biến do con trỏ trỏ đến để thao tác Dùng con trỏ để lưu địa chỉ của biến Bản thân con trỏ sẽ được trỏ vào địa chỉ của một biến có cùng kiểu dữ liệu với nó. Cú pháp của phép gán như sau: <Tên con trỏ> = &<tên biến>; Lưu ý • Trong phép toán này, tên con trỏ không có dấu “*”. Ví dụ: int x, *px; px = &x; sẽ cho con trỏ px có kiểu int trỏ vào địa chỉ của biến x có kiểu nguyên. Phép toán &<Tên biến> sẽ cho địa chỉ của biến tương ứng.
Lấy giá trị của biến do con trỏ trỏ đến Phép lấy giá trị của biến do con trỏ trỏ đến được thực hiện bằng cách gọi tên: *<Tên con trỏ>; Lưu ý • Trong phép toán này, phải có dấu con trỏ “*”. Nếu không có dấu con trỏ, sẽ trở thành phép lấy địa chỉ của biến do con trỏ trỏ tới. Ví dụ: int x = 12, y, *px; px = &y; *px = x; Quá trình diễn ra như sau: int x = 12, y, *px; x = 12 y=0 px null px = &y; x = 12 y=0 px px = x; x = 12 y=x =12 px con trỏ px vẫn trỏ tới địa chỉ biến y và giá trị của biến y sẽ là 12. Phép gán giữa các con trỏ Các con trỏ cùng kiểu có thể gán cho nhau thông qua phép gán và lấy địa chỉ con trỏ: <Tên con trỏ 1> = <Tên con trỏ 2>; Lưu ý • Trong phép gán giữa các con trỏ, bắt buộc phải dùng phép lấy địa chỉ của biến do con trỏ trỏ tới (không có dấu “*” trong tên con trỏ) mà không được dùng phép lấy giá trị của biến do con trỏ trỏ tới.com https://fb.com/tailieudientucntt Chương 2: Con trỏ và mảng • Hai con trỏ phải cùng kiểu.
Trong trường hợp hai con trỏ khác kiểu, phải sử dụng các phương thức ép kiểu tương tự như trong phép gán các biến thông thường có kiểu khác nhau. Ví dụ: int x = 12, *px, *py; px = &x; py = px; int x = 12, *px, *py; x = 12 px null py null px = &x; x = 12 px py null py = px; x = 12 px py con trỏ py cũng trỏ vào địa chỉ của biến x như con trỏ px. Khi đó *py cũng có giá trị 12 giống như *px và là giá trị của biến x. Chương trình 2.1 minh hoạ việc dùng con trỏ giữa các biến của một chương trình C++.
Chương trình 2.h> void main(void){ int x = 12, *px, *py; cout << ”x = ” << x << endl; px = &x; // Con trỏ px trỏ tới địa chỉ của x cout << ”px = &x, *px = ” << *px << endl; *px = *px + 20; // Nội dung của px là 32 cout << ”*px = *px+20, x = ” << x << endl; py = px; // Cho py trỏ tới chỗ mà px trỏ: địa chỉ của x *py += 15; // Nội dung của py là 47 cout << ”py = px, *py +=15, x = ” << x << endl; } Trong chương trình 2.1, ban đầu biến x có giá trị 12. Sau đó, con trỏ px trỏ vào địa chỉ của biến x nên con trỏ px cũng có giá trị 12. Tiếp theo, ta tăng giá trị của con trỏ px thêm 20, giá trị của con trỏ px là 32. Vì px đang trỏ đến địa chỉ của x nên x cũng có giá trị là 32.
Sau đó, ta cho con trỏ py trỏ đến vị trí mà px đang trỏ tới (địa chỉ của biến x) nên py cũng có giá trị 32. Cuối cùng, ta tăng giá trị của con trỏ py thêm 15, py sẽ có giá trị 37. Vì py cũng đang trỏ đến địa chỉ của x nên x cũng có giá trị 37.1 sẽ in ra kết quả như sau: 13 CuuDuongThanCong.com https://fb.com/tailieudientucntt Chương 2: Con trỏ và mảng x = 12 px = &x, *px = 12 *px = *px + 20, x = 32 py = px, *py += 15, x = 37 2.2 CON TRỎ VÀ MẢNG 2.1 Con trỏ và mảng một chiều Mảng một chiều Trong C++, tên một mảng được coi là một kiểu con trỏ hằng, được định vị tại một vùng nhớ xác định và địa chỉ của tên mảng trùng với địa chỉ của phần tử đầu tiên của mảng. Ví dụ khai báo: int A[5]; thì địa chỉ của mảng A (cũng viết là A) sẽ trùng với địa chỉ phần tử đầu tiên của mảng A (là &A[0]) nghĩa là: A = &A[0]; Quan hệ giữa con trỏ và mảng Vì tên của mảng được coi như một con trỏ hằng, nên nó có thể được gán cho một con trỏ có cùng kiểu.
Ví dụ khai báo: int A[5] = {5, 10, 15, 20, 25}; int *pa = A; int A[5] = {5, 10, 15, 20, 25}; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25 int *pa = A; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25 pa thì con trỏ pa sẽ trỏ đến mảng A, tức là trỏ đến địa chỉ của phần tử A[0], cho nên hai khai báo sau là tương đương: pa = A; pa = &A[0]; Với khai báo này, thì địa chỉ trỏ tới của con trỏ pa là địa chỉ của phần tử A[0] và giá trị của con trỏ pa là giá trị của phần tử A[0], tức là *pa = 5; Phép toán trên con trỏ và mảng Khi một con trỏ trỏ đến mảng, thì các phép toán tăng hay giảm trên con trỏ sẽ tương ứng với phép dịch chuyển trên mảng. Ví dụ khai báo: int A[5] = {5, 10, 15, 20, 25}; 14 CuuDuongThanCong.com https://fb.com/tailieudientucntt Chương 2: Con trỏ và mảng int *pa = &A[2]; thì con trỏ pa sẽ trỏ đến địa chỉ của phần tử A[2] và giá trị của pa là: *pa = A[2] = 15. Khi đó, phép toán: pa = pa + 1; sẽ đưa con trỏ pa trỏ đến địa chỉ của phần tử tiếp theo của mảng A, đó là địa chỉ của A[3]. Sau đó, phép toán: pa = pa – 2; sẽ đưa con trỏ pa trỏ đến địa chỉ của phần tử A[1].
int A[5] = {5, 10, 15, 20, 25}; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25 int *pa = &A[2]; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25 pa pa = pa + 1; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25 pa pa = pa - 2; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25 pa Lưu ý: • Hai phép toán pa++ và *pa++ có tác dụng hoàn toàn khác nhau trên mảng, pa++ là thao tác trên con trỏ, tức là trên bộ nhớ, nó sẽ đưa con trỏ pa trỏ đến địa chỉ của phần tử tiếp theo của mảng. *pa++ là phép toán trên giá trị, nó tăng giá trị hiện tại của phần tử mảng lên một đơn vị. Ví dụ: int A[5] = {5, 10, 15, 20, 25}; int *pa = &A[2]; thì pa++ là tương đương với pa = &A[3] và *pa = 20. nhưng *pa++ lại tương đương với pa = &A[2] và *pa = 15+1 = 16, A[2] = 16.com https://fb.com/tailieudientucntt Chương 2: Con trỏ và mảng int A[5] = {5, 10, 15, 20, 25}; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25 int *pa = &A[2]; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25 pa pa ++; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25 pa Nhưng *pa ++; A A[0]=5 A[1]=10 A[2]=16 A[3]=20 A[4]= 25 pa • Trong trường hợp: int A[5] = {5, 10, 15, 20, 25}; int *pa = &A[4]; thì phép toán pa++ sẽ đưa con trỏ pa trỏ đến một địa chỉ không xác định.
Lí do là A[4] là phần tử cuối của mảng A, nên pa++ sẽ trỏ đến địa chỉ ngay sau địa chỉ của A[4], địa chỉ này nằm ngoài vùng chỉ số của mảng A nên không xác định. Tương tự với trường hợp pa=&A[0], phép toán pa-- cũng đưa pa trỏ đến một địa chỉ không xác định. int A[5] = {5, 10, 15, 20, 25}; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25 int *pa = &A[2]; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25 pa pa ++; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25 null pa pa = &A[0]; A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25 pa pa --; null A A[0]=5 A[1]=10 A[2]=15 A[3]=20 A[4]= 25 pa • Vì mảng A là con trỏ hằng, cho nên không thể thực hiện các phép toán trên A mà chỉ có thể thực hiện trên các con trỏ trỏ đến A: các phép toán pa++ hoặc pa--là hợp lệ, nhưng các phép toán A++ hoặc A--là không hợp lệ. Chương trình 2.