Nhập môn lập trình - Bài 14: Con trỏ

1. Cho biết ý nghĩa của các khai báo và câu lệnh; Tìm lỗi sai trong đoạn code và giải thích (t.t)  xem các bài tập từ 1 đến 12 trong phần trước. 2. Viết chương trình nhập một dãy số hữu tỉ tùy ý (sử dụng con trỏ và sự cấp phát động), xuất ra dãy gồm tất cả các số nhỏ hơn 1 có trong dãy được nhập vào, tính tổng và tích của dãy số hữu tỉ. 3. Viết chương trình khai báo mảng hai chiều có 12x12 phần tử kiểu char. Gán ký tự „X‟ cho mọi phần tử của mảng này. Sử dụng con trỏ đến mảng để in giá trị các phần tử mảng lên màn hình ở dạng lưới.

pdf93 trang | Chia sẻ: dntpro1256 | Lượt xem: 1411 | Lượt tải: 0download
Bạn đang xem trước 20 trang tài liệu Nhập môn lập trình - Bài 14: Con trỏ, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
NHẬP MÔN LẬP TRÌNH BÀI 14: CON TRỎ CĐR buổi học • Sau khi học xong buổi học, sinh viên có khả năng: • Hiểu được khái niệm con trỏ, địa chỉ của biến và quản lý các biến trong C/C++. • Sử dụng con trỏ trong lập trình • Biết được một số thuật ngữ và tiếng Anh tương ứng 2 Bảng các thuật ngữ Việt- Anh liên quan nội dung con trỏ Thuật ngữ tiếng Việt Thuật ngữ tiếng Anh Con trỏ Pointer Hằng con trỏ Constant pointer Địa chỉ bộ nhớ Memory Address Toán tử & Address-of Operator Toán tử * Dereferencing Operator, or: Indirection Operator Cấp phát bộ nhớ Memory Allocation Giải phóng bộ nhớ De-Allocate Memory Cấp phát tĩnh Static Memory Allocation Cấp phát động Dynamic Memory Allocation Biến động Dynamic Variable Phép toán số học trên con trỏ Pointer Arithmetic NMLT - CON TRỎ CƠ BẢN 3 Nội dung 1. Khái niệm và cách sử dụng con trỏ 2. Con trỏ và mảng 1 chiều 3. Bài tập NMLT - Con trỏ và cấp phát động 4 1. Khái niệm và cách sử dụng 1.1 Biến và vùng nhớ 1.2 Khái niệm con trỏ 1.3 Khai báo con trỏ 1.4 Con trỏ và toán tử &, * 1.5 Con trỏ NULL 1.6 Kích cỡ con trỏ 1.7 Từ khóa const và con trỏ 1.8 Con trỏ và hàm Bài tập Một số lưu ý NMLT - CON TRỎ CƠ BẢN 5 1.1 Biến và vùng nhớ • Bộ nhớ máy tính • Bộ nhớ RAM chứa rất nhiều ô nhớ, mỗi ô nhớ có kích thước 1 byte. • Mỗi ô nhớ có địa chỉ duy nhất và địa chỉ này được đánh số từ 0 trở đi. • RAM để lưu trữ mã chương trình và dữ liệu trong suốt quá trình thực thi. NMLT - CON TRỎ CƠ BẢN 6 Memory Layout (bytes) . . . 0 1 2 3 4 5 6 7 . . . Địa chỉ ô nhớ 1 byte 1.1 Biến và vùng nhớ • Khi khai báo biến, máy tính sẽ dành riêng một vùng nhớ để lưu biến đó. • Khi tên biến được gọi, máy tính sẽ thực hiện 2 bước sau: • Tìm kiếm địa chỉ ô nhớ của biến. • Truy xuất hoặc thiết lập giá trị của biến được lưu trữ tại ô nhớ đó. NMLT - CON TRỎ CƠ BẢN 7 1.1 Biến và vùng nhớ NMLT - CON TRỎ CƠ BẢN 8 int main() { char ch=‘x’; int a = 7; } Memory Layout (bytes) . . . 0 1 2 3 4 5 6 7 . . . Địa chỉ ô nhớ ch a x 7 Toán tử & và * • Toán tử & (Address-of Operator) đặt trước tên biến và cho biết địa chỉ của vùng nhớ của biến. • Toán tử * (Dereferencing Operator hay Indirection Operator) đặt trước một địa chỉ và cho biết giá trị lưu trữ tại địa chỉ đó. • Ví dụ: NMLT - CON TRỎ CƠ BẢN 9 Toán tử & và * cout << " value = " << value; => value = 3200; cout << " &value = " << &value; => &value = 0x50; cout << " *(&value) = " << *(&value); => *(&value) = 3200; NMLT - CON TRỎ CƠ BẢN 10 int value; value = 3200; 3200 value 0x50 Memory Layout 1.2 Khái niệm con trỏ • Khái niệm: Con trỏ (Pointer) là một biến lưu trữ địa chỉ của một địa chỉ bộ nhớ. Địa chỉ này thường là địa chỉ của một biến khác. VD: Biến x chứa địa chỉ của biến y. Vậy ta nói biến x “trỏ tới” y. • Phân loại con trỏ: Con trỏ kiểu int dùng để chứa địa chỉ của các biến kiểu int. Tương tự ta có con trỏ kiểu float, double, NMLT - CON TRỎ CƠ BẢN 11 1.3 Khai báo con trỏ • Khai báo • Giống như mọi biến khác, biến con trỏ muốn sử dụng cũng cần phải được khai báo. • Ví dụ NMLT - CON TRỎ CƠ BẢN 12 *; char char1; int *ptrI; float *ptrF; char1 0x50 ptrF 0x10 ptrI 0x80 Memory Layout 1.4 Con trỏ và toán tử &, * • Toán tử & dùng trong khởi tạo giá trị cho con trỏ • Ví dụ: NMLT - CON TRỎ CƠ BẢN 13 * = & ; int a; int *ptr = &a; double a; int *ptr = &a;? 0x34ptr 0x90 Memory Layout a 0x34 1.4 Con trỏ và toán tử &, * • Toán tử * đặt trước biến con trỏ cho phép truy xuất đến giá trị ô nhớ mà con trỏ trỏ đến. • Ví dụ NMLT - CON TRỎ CƠ BẢN 14 int a = 1000; int *ptr = &a; cout << ptr << ‚ ‛ << *ptr; // a = 3200 *ptr = 3200; cout << *ptr; (*ptr) ++; 1000 0x34 a ptr 0x34 0x90 Memory Layout 32 1 Ví dụ #include using namespace std; int main() { int a; int *ptr; int value; a = 3200; ptr = &a; value = --(*ptr); } NMLT - CON TRỎ CƠ BẢN 15 Memory Layout Ví dụ #include using namespace std; int main() { int a; int *ptr; int value; a = 3200; ptr = &a; value = --(*ptr); } NMLT - CON TRỎ CƠ BẢN 16 3200 3199 0x34 a ptr value 0x34 0x90 0x50 Memory Layout 199 Ví dụ NMLT - CON TRỎ CƠ BẢN 17 3199 3199 0x34 a ptr value 0x34 0x90 0x50 3199 0x34 3199 0x50 0x90 0x34 3199 0x34 error 3199 Memory Layout value = ptr = a = &value = &ptr = &a = *ptr = &(*ptr) = *(*ptr) = *(&(*ptr)) = Phép gán con trỏ • Có thể gán biến con trỏ: int *p1, *p2; p2 = p1;  Gán một con trỏ cho con trỏ khác  “Chỉ định p2 trỏ tới nơi mà p1 đang trỏ tới“ Dễ bị lẫn với: *p2 = *p1;  Gán “giá trị trỏ bởi p1” cho “giá trị trỏ bởi p2” NMLT - CON TRỎ CƠ BẢN 18 Ví dụ NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 19 p1 p2 5 27 p1 p2 5 27 p1 = p2 p1 p2 5 27 p1 p2 5 5 *p1 = *p2 1.5 Con trỏ NULL • Khái niệm • Con trỏ NULL là con trỏ không trỏ vào đâu cả. • Khác với con trỏ chưa được khởi tạo. int n; int *p1 = &n; int *p2; // unreferenced local variable int *p3 = NULL; NMLT - CON TRỎ CƠ BẢN 20 NULL 1.6 Toán tử sizeof • Để xác định kích thước (bytes) của một kiểu dữ liệu ta dùng toán tử sizeof. Cú pháp: sizeof (type) hoặc sizeof value Trong đó type là kiểu dữ liệu, value là tên biến • Kích thước của kiểu dữ liệu không giống nhau cho tất cả máy tính. Nên dùng toán tử sizeof để biết chính xác kích thước của dữ liệu. • Con trỏ chỉ lưu địa chỉ nên kích thước của mọi con trỏ là như nhau. (Kết quả sau mang tính chất tham khảo) NMLT - CON TRỎ CƠ BẢN 21 int a; double b; char c; int *pa; double *pb; char *pc; sizeof a = 4 sizeof(b) = 8 sizeof(c) = 1 sizeof pa = 4 sizeof pb = 4 sizeof(pc)= 4 sizeof(int) = 4 sizeof(double) = 8 sizeof(char) = 1 sizeof(int*) = 4 sizeof(double*)= 4 sizeof(char*) = 4 1.7 Từ khóa const và con trỏ • Hằng số dùng trong khai báo một biến cho biết giá trị của biến không được phép thay đổi trong quá trình thực hiện chương trình. • Tùy thuộc vào vị trí đặt từ khóa const dùng trong khái báo biến con trỏ, mà quy định giá trị hằng cho con trỏ hay cho vùng nhớ con trỏ trỏ tới. • Có 3 trường hợp trong khai báo biến con trỏ và từ khóa const. NMLT - CON TRỎ CƠ BẢN 22 1.7 Con trỏ và hàm • Xét ví dụ sau: Hãy viết hàm để nhập giá trị cho 1 biến. Cách viết nội dung hoàn toàn ở hàm main như sau: int main() { int a; cout << "Nhap gia tri vao"; cin >> a; cout << a; } NMLT - CON TRỎ CƠ BẢN 23 a 0x50 1.7 Con trỏ và hàm // Cách 1: int NhapGiaTri(){ int b; cout << "Nhap gia tri vao"; cin >> b; return b; } int main() { int a; a = NhapGiaTri(); cout << a; } NMLT - CON TRỎ CƠ BẢN 24 a 0x50 5 b 0x100 int NhapGiaTri() 1.7 Con trỏ và hàm ? Hỏi cách này có đúng không void NhapGiaTri(int b) { cout << "Nhap gia tri vao"; cin >> b; } int main() { int a; NhapGiaTri(a); cout << a; } NMLT - CON TRỎ CƠ BẢN 25 1.7 Con trỏ và hàm // Cách 2 void NhapGiaTri(int *b) { cout << "Nhap gia tri vao"; cin >> *b; } int main() { int a; NhapGiaTri(&a); cout << a; } NMLT - CON TRỎ CƠ BẢN 26 1.7 Con trỏ và hàm // Cách 3 void NhapGiaTri(int &b) { cout << "Nhap gia tri vao"; cin >> b; } int main() { int a; NhapGiaTri(a); cout << a; } NMLT - CON TRỎ CƠ BẢN 27 Bài tập 1 • Tìm lỗi sai trong đoạn chương trình sau: int main() { int x, *p; x = 10; *p = x; return 0; } NMLT - CON TRỎ CƠ BẢN 28 Bài tập 2 #include using namespace std; int main() { int i = 12; int *p1; // i = 24; p1 = &i; *p1 = 24; cout << *p1 << " " << i << endl; } NMLT - CON TRỎ CƠ BẢN 29 Dùng C++ viết một đoạn chương trình với 2 biến: + Biến i có kiểu int với giá trị khởi đầu là 12 + Biến p1 là một con trỏ trỏ tới vùng nhớ kiểu int. ? Hãy dùng biến p1 để thay đổi giá trị của biến i từ 12 sang 24. Bài tập 3 • Hãy viết hàm hoán đổi giá trị của 2 tham số • Giải: NMLT - CON TRỎ CƠ BẢN 30 // Cách 1 void Swap(int *a, int *b) { int temp = *b; *b = *a; *a = temp; } int main() { int x=7, y=8; Swap(&x, &y); cout << "x= "<< x << ", y= " << y; } //Cách 2 void Swap(int &a, int &b) { int temp = b; b = a; a = temp; } int main() { int x=7, y=8; Swap(x, y); cout << "x= "<< x << ", y= " << y; } Một số lưu ý • Con trỏ là khái niệm quan trọng và khó nhất trong C++. Mức độ thành thạo C++ được đánh giá qua mức độ sử dụng con trỏ. • Nắm rõ quy tắc sau, ví dụ int a, *pa = &a; • *pa và a đều chỉ nội dung của biến a. • pa và &a đều chỉ địa chỉ của biến a. • Không nên sử dụng con trỏ khi chưa được khởi tạo. Kết quả sẽ không lường trước được. int *pa; *pa = 1904; => sai NMLT - CON TRỎ CƠ BẢN 31 2. Con trỏ và Mảng 1 chiều 2.1 Mảng 1 chiều và cách lấy địa chỉ 2.2 Mảng 1 chiều và hằng con trỏ 2.3 Các phép toán số học trên con trỏ 2.4 Con trỏ và mảng 1 chiều Bài tập Một số lưu ý NMLT - CON TRỎ CƠ BẢN 32 2.1 Mảng 1 chiều và cách lấy địa chỉ • Cho mảng 1 chiều: int arr[6] = {5, 6, 9, 4, 1, 2}; NMLT - CON TRỎ CƠ BẢN 33 Memory Layout 2 1 4 9 6 5 0x30 0x26 0x22 0x18 0x14 0x10 arr[5] arr[4] arr[3] arr[2] arr[1] arr[0] &arr[5] &arr[4] &arr[3] &arr[2] &arr[1] &arr[0] Lấy giá trịLấy địa chỉ 2.2 Mảng 1 chiều và hằng con trỏ • Cho mảng 1 chiều • Tên mảng arr là một hằng con trỏ  không thể thay đổi giá trị của hằng này. • arr là địa chỉ đầu tiên của mảng arr == &arr[0] NMLT - CON TRỎ CƠ BẢN 34 int arr[6] = {5, 6, 9, 4, 1, 2}; 2 1 4 9 6 5 0x30 0x26 0x22 0x18 0x14 0x10 arr == &arr[0] = 0x10 Memory Layout 2.2 Mảng 1 chiều và hằng con trỏ NMLT - CON TRỎ CƠ BẢN 35 2 1 4 9 6 5 0x30 0x26 0x22 0x18 0x14 0x10 arr[5] arr[4] arr[3] arr[2] arr[1] arr[0] = *(arr+5) = *(arr+4) = *(arr+3) = *(arr+2) = *(arr+1) = * arr = *(arr+0) Memory Layout Lấy giá trị 2.3 Các phép toán số học trên con trỏ Phép toán số học trên con trỏ- Pointer Arithmetics • Phép cộng (tăng) • + n  + n * sizeof() • Có thể sử dụng toán tử gộp += hoặc ++ • Phép trừ (giảm) • – n  – n * sizeof() • Có thể sử dụng toán tử gộp –= hoặc – – NMLT - CON TRỎ CƠ BẢN 36 2.3 Các phép toán số học trên con trỏ NMLT - CON TRỎ CƠ BẢN 37 2 1 4 9 6 5 0x30 0x26 0x22 0x18 0x14 0x10 p+1 (=0x22) arr int *p = &arr[2] Memory Layout p+2 (=0x26) 2.3 Các phép toán số học trên con trỏ NMLT - CON TRỎ CƠ BẢN 38 2 1 4 9 6 5 0x30 0x26 0x22 0x18 0x14 0x10 p-1 (=0x22) arr p-2 (=0x18) Memory Layout int *p = &arr[4] 2.3 Các phép toán số học trên con trỏ • Phép toán tính khoảng cách giữa 2 con trỏ • *p1, *p2; • p1 – p2 cho ta khoảng cách (theo số phần tử) giữa hai con trỏ (cùng kiểu) • Các phép toán so sánh • Phép so sánh: So sánh địa chỉ giữa hai con trỏ (thứ tự ô nhớ) ==, !=, >, >=, <, <= • Không thể thực hiện các phép toán: * / % NMLT - CON TRỎ CƠ BẢN 39 2.3 Các phép toán số học trên con trỏ NMLT - CON TRỎ CƠ BẢN 40 2 1 4 9 6 5 0x30 0x26 0x22 0x18 0x14 0x10 arr Memory Layout int *p2 = &arr[5] int *p1 = &arr[1] p2 – p1 = 4 p1 – p2 = -4 2.4 Con trỏ và mảng 1 chiều int arr[6] = {5, 6, 9, 4, 1, 2}; int *parr; // Cách 1 parr = arr; // Cách 2 parr = &arr[0]; NMLT - CON TRỎ CƠ BẢN 41 0x10 parr 0x90 2 1 4 9 6 5 0x30 0x26 0x22 0x18 0x14 0x10 arr Memory Layout  Xét đoạn code sau: 2.4 Con trỏ và mảng 1 chiều • Truy xuất tới giá trị của phần tử thứ i của mảng (xét i là chỉ số hợp lệ của mảng): • Truy xuất tới địa chỉ của phần tử thứ i của mảng (xét i là chỉ số hợp lệ của mảng): NMLT - CON TRỎ CƠ BẢN 42 arr[i] == *(arr+i) == parr[i] == *(parr + i) &arr[i] == arr+i == &parr[i] == parr + i 2.4 Con trỏ và mảng 1 chiều NMLT - CON TRỎ CƠ BẢN 43 2 1 4 9 6 5 0x30 0x26 0x22 0x18 0x14 0x10 arr[5] arr[4] arr[3] arr[2] arr[1] arr[0] = parr[5] = parr[4] = parr[3] = parr[2] = parr[1] = parr[0] Memory Layout = *(parr+5) = *(parr+4) = *(parr+3) = *(parr+2) = *(parr+1) = *(parr+0) 2.5 Truyền mảng 1 chiều cho hàm • Xét 2 đoạn chương trình sau. Tìm lỗi sai và giải thích. Lý do: Đối số mảng truyền cho hàm không phải hằng con trỏ. NMLT - CON TRỎ CƠ BẢN 44 int main() { int a[] = {1,2,3,4,5,6}; for (int i = 0; i<6; i++) printf("%d", *(a++)); } void xuat(int *a, int n) { for (int i = 0; i<n; i++) printf("%d", *(a++)); } int main() { int a[] = {1,2,3,4, 5, 6}; xuat(a, 6); } SAI ĐÚNG Bài tập 1 Nhóm các lệnh sau lại thành 2 nhóm tương ứng nhóm lấy địa chỉ biến và nhóm lấy giá trị biến: *(parr+i) arr[i] &parr[i] &arr[i] parr[i] arr+i parr + i *(arr+i) NMLT - CON TRỎ CƠ BẢN 45 Bài tập 2 Cho mảng 1 chiều a có 10 phần tử, biến con trỏ p trỏ tới mảng 1 chiều a. a. Hãy dùng con trỏ p để gán giá trị 100 cho phần tử thứ 5 của mảng. b. Hãy viết chương trình nhập và xuất mảng 1 chiều thông qua con trỏ p. NMLT - CON TRỎ CƠ BẢN 46 Lời giải #include using namespace std; const int n = 10; int main() { int a[n], *p = a; *(p+5) =100; // câu a for (int i = 0; i < n; i++) {// câu b cin >> *(p + i); } for (int i = 0; i < n; i++) {// câu b cout << *(p + i) << " "; } } NMLT - CON TRỎ CƠ BẢN 47 Bài tập 2 • Tạo biến str lưu chuỗi “hello class”, sau đó tạo biến con trỏ p lưu trữ địa chỉ đầu tiên của chuỗi. Hãy thực hiện chuyển chuỗi str thành chuỗi ký tự in hoa “HELLO CLASS” thông qua sử dụng con trỏ p. NMLT - CON TRỎ CƠ BẢN 48 Lời giải #include using namespace std; int main() { char str[20] = "hello class"; char *p; int n = strlen(str); p = str; for (int i = 0; i<=n; i++) p[i] = toupper(p[i]); cout << p; } NMLT - CON TRỎ CƠ BẢN 49 Một số lưu ý • Không thực hiện các phép toán nhân, chia, lấy phần dư. • Tăng/giảm con trỏ n đơn vị có nghĩa là tăng/giảm giá trị của nó n*sizeof () • Không thể tăng/giảm biến mảng. Hãy gán một con trỏ đến địa chỉ đầu của mảng và tăng/giảm nó. NMLT - CON TRỎ CƠ BẢN 50 Câu hỏi lý thuyết • Bài 1: Toán tử nào dùng để xác định địa chỉ của một biến? • Bài 2: Toán tử nào dùng để xác định giá trị của biến do con trỏ trỏ đến? • Bài 3: Phép lấy giá trị gián tiếp là gì? • Bài 4: Cho biến daa kiểu int. Khai báo và khởi tạo con trỏ pdaa trỏ đến biến này. Sau đó gán giá trị 100 cho biến daa sử dụng hai cách trực tiếp và gián tiếp. • Bài 5: Các phần tử trong mảng được sắp xếp trong bộ nhớ như thế nào? • Bài 6: Cho mảng một chiều data. Trình bày 2 cách lấy địa chỉ phần tử đầu tiên của mảng này. • Bài 7: Cho con trỏ p1 trỏ đến phần tử thứ 3 còn con trỏ p2 trỏ đến phần tử thứ 4 của mảng int. Tính p2 – p1 ? NMLT - CON TRỎ CƠ BẢN 51 Câu hỏi lý thuyết • Bài 8: Tìm lỗi trong đoạn code sau: #include using namespace std; int main() { int *x, y = 2; *x = y; *x += y++; cout << *x << y; } NMLT - CON TRỎ CƠ BẢN 52 • Bài 9: Cho đoạn chương trình sau: float pay; float *ptr_pay; pay = 2313.54; ptr_pay = &pay; Hãy cho biết giá trị của: a. pay b. *ptr_pay c. *pay d. &pay Bài tập bắt buộc 1. Cho biết ý nghĩa của các khai báo và câu lệnh; Tìm lỗi sai trong đoạn code và giải thích 2. Cho mảng 1 chiều a có 10 phần tử, biến con trỏ p trỏ tới mảng 1 chiều a. Hãy dùng con trỏ p để gán giá trị 100 cho phần tử thứ 5 của mảng. Hãy viết chương trình nhập và xuất mảng 1 chiều thông qua con trỏ p. 3. Tạo biến str lưu chuỗi “hello class”, sau đó tạo biến con trỏ p lưu trữ địa chỉ đầu tiên của chuỗi. Hãy thực hiện chuyển chuỗi str thành chuỗi ký tự in hoa “HELLO CLASS” thông qua sử dụng con trỏ p. 4. Viết hàm nhập một dãy số thực A tùy ý trong đó có sự cấp phát động. Viết hàm sao chép dãy số thực A (được nhập bởi hàm trên) sang một dãy B trong đó có sự giải phóng vùng nhớ cấp phát động và cấp phát lại ở dãy B. 5. Làm lại các bài tập về mảng dùng con trỏ NMLT - CON TRỎ CƠ BẢN 53 NHẬP MÔN LẬP TRÌNH BUỔI 15: CON TRỎ VÀ CẤP PHÁT ĐỘNG CĐR buổi học • Sau khi học xong buổi học, sinh viên có khả năng: • Hiểu được về con trỏ và cấp phát động. • Áp dụng con trỏ trong cấp phát mảng. • Áp dụng con trỏ và tham số của hàm. • Áp dụng con trỏ và cấu trúc. 55 Nội dung 1. Cấp phát động 2. Cấp phát động mảng 1 chiều 3. Cấp phát động mảng 2 chiều 4. Con trỏ và hàm số 5. Con trỏ và cấu trúc 6. Một số vấn đề mở rộng NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 56 1. Cấp phát động • Cấp phát bộ nhớ tĩnh (static memory allocation) • Khai báo biến, cấu trúc, mảng, • Bắt buộc phải biết trước cần bao nhiều bộ nhớ lưu trữ tốn bộ nhớ, không thay đổi được kích thước, • Cấp phát động (dynamic memory allocation) • Cần bao nhiêu cấp phát bấy nhiêu. • Có thể giải phóng nếu không cần sử dụng. • Sử dụng vùng nhớ ngoài chương trình (cả bộ nhớ ảo virtual memory). NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 57 Cấu trúc một CT C++ trong bộ nhớ • Toàn bộ tập tin chương trình sẽ được nạp vào bộ nhớ tại vùng nhớ còn trống, gồm 4 phần: NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 58 STACK Last-In First-Out Vùng cấp phát tĩnh (kích thước cố định) Vùng cấp phát động (RAM trống và bộ nhớ ảo) Gồm các lệnh và hằng (kích thước cố định) Lưu đối tượng cục bộ khi thực hiện hàm Vùng nhớ trống HEAP Đối tượng toàn cục & tĩnh Mã chương trình Cấp phát bộ nhớ • Cấp phát bộ nhớ • Trong C: Hàm malloc, calloc, realloc ( hoặc ) • Trong C++: Toán tử new • Giải phóng bộ nhớ • Trong C: Hàm free • Trong C++: Toán tử delete NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 59 Biến cấp phát động và Biến tự động • Biến cục bộ • Khai báo bên trong định nghĩa hàm • Sinh ra khi hàm được gọi • Hủy đi khi hàm kết thúc • Thường gọi là biến tự động nghĩa là được trình biên dịch quản lý một cách tự động • Biến cấp phát động • Sinh ra bởi cấp phát động • Sinh ra và hủy đi khi chương trình đang chạy • Biến cấp phát động hay Biến động là biến con trỏ trước khi sử dụng được cấp phát bộ nhớ. NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 60 Toán tử new • Vì con trỏ có thể tham chiếu tới biến nhưng không thực sự cần phải có định danh cho biến đó. • Có thể cấp phát động cho biến con trỏ bằng toán tử new. Toán tử new sẽ tạo ra biến “không tên” cho con trỏ trỏ tới. • Cú pháp: * = new Ví dụ: int *ptr = new int; o Tạo ra một biến “không tên” và gán ptr trỏ tới nó o Có thể làm việc với biến “không tên” thông qua *ptr NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 61 Kiểm tra việc cấp phát có thành công không #include using namespace std; int main() { int *p = new int; if (p == NULL) { cout << "Error: Khong du bo nho.\n"; exit(1); } *p = 3199; } NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 62 3199 0x34ptr 0x34 0x90 Khởi tạo giá trị trong cấp phát động • Cú pháp: pointer = new (value) • Ví dụ: #include using namespace std; int main() { int *p; p = new int(99); // initialize with 99 cout << *p; // displays 99 return 0; } NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 63 Ví dụ NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 64 ?p1 ?p2 1. int *p1, *p2; p1 ?p2 2. int *p1 = new int; ? p1 ?p2 3. *p1 = 30; p1 p2 4. p2 = p1; p1 p2 5. *p2 = 40; p1 p2 6. p1 = new int; 30 30 40 40 ? p1 p2 7. *p1 = 50; 40 50 Toán tử delete • Toán tử delete dùng để giải phóng vùng nhớ trong HEAP do con trỏ trỏ tới (con trỏ được cấp pháp bằng toán tử new). Cú pháp: delete ; • Ghi chú: Sau khi gọi toán tử delete thì con trỏ vẫn trỏ tới vùng nhớ trước khi gọi hàm delete. Ta gọi là “con trỏ lạc”. Ta vẫn có thể gọi tham chiếu trên con trỏ, tuy nhiên: • Kết quả không lường trước được • Thường là nguy hiểm  Hãy tránh con trỏ lạc bằng cách gán con trỏ bằng NULL sau khi delete. • Ví dụ: delete pointer; pointer = NULL; NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 65 Từ khóa typedef • Từ khóa typedef dùng để định nghĩa 1 tên mới hay gọi là một biệt danh (alias) cho tên kiểu dữ liệu có sẵn. Ví dụ: typedef int SONGUYEN; Các khai báo sau tương đương: int a; SONGUYEN a; NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 66 Định nghĩa kiểu dữ liệu con trỏ • Có thể đặt tên cho kiểu dữ liệu con trỏ • Để có thể khai báo biến con trỏ như các biến khác • Loại bỏ * trong khai báo con trỏ Ví dụ: typedef int* IntPtr; - Định nghĩa một tên khác cho kiểu dữ liệu con trỏ - Các khai báo sau tương đương: IntPtr p; int *p; NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 67 Con trỏ và hàm • Con trỏ là kiểu dữ liệu hoàn chỉnh có thể dùng nó như các kiểu khác • Con trỏ có thể là tham số của hàm • Có thể là kiểu trả về của hàm Ví dụ: int* findOtherPointer(int* p); Hàm này khai báo: - Có tham số kiểu con trỏ trỏ tới int - Trả về biến con trỏ trỏ tới int NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 68 Ví dụ typedef int *IntPointer; void Input(IntPointer temp) { *temp = 20; cout << "Trong ham goi *temp = " << *temp << endl; } int main() { IntPointer p = new int; *p = 10; cout << "Truoc khi goi ham, *p = " << *p << endl; Input(p); cout << "Sau khi ket thuc ham, *p = " << *p << endl; } NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 69 Truoc khi goi ham, *p = 10 Trong ham goi *temp = 20 Sau khi ket thuc ham, *p = 20 Ví dụ NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 70 p 1. Trước khi gọi hàm Input 10 p 10 temp 2. Giá trị của p sẽ được truyền vào temp p 20 temp 3. Thay đổi giá trị *temp p 20 4. Sau khi kết thúc gọi hàm Input Nhắc lại • Mảng lưu trong các ô nhớ liên tiếp trong bộ nhớ máy tính • Biến mảng tham chiếu tới phần tử đầu tiên • Biến mảng là một biến hằng con trỏ NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 71 Nhắc lại • Ví dụ: int a[10]; typedef int* IntPtr; IntPtr p;  a và p là các biến con trỏ. • Phép gán hợp lệ p = a; • p bây giờ sẽ trỏ tới nơi a trỏ, tức là tới phần tử đầu tiên của mảng a • Phép gán không hợp lệ a = p; • Bởi con trỏ mảng là con trỏ hằng. NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 72 Hạn chế của mảng chuẩn • Hạn chế của mảng chuẩn • Bắt buộc phải biết trước cần bao nhiều bộ nhớ lưu trữ => tốn bộ nhớ, không thay đổi được kích thước,  Dùng Mảng động • Mảng động • Kích thước không xác định ở thời điểm lập trình • Mà xác định khi chạy chương trình NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 73 Tạo mảng động bằng toán tử new • Cấp phát động cho biến con trỏ • Sau đó dùng con trỏ như mảng chuẩn • Cú pháp: = new [] Ví dụ: typedef double * doublePtr; doublePtr d; d = new double[10];  Tạo biến mảng cấp phát động d có 10 phần tử, kiểu cơ sở là double. NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 74 Xóa mảng động • Dùng toán tử delete[] để xóa mảng động. Ví dụ: double *d = new double[10]; //... Processing delete[] d;  Giải phóng tất cả vùng nhớ của mảng động này  Cặp ngoặc vuông báo hiệu có mảng  Nhắc lại: d vẫn trỏ tới vùng nhớ đó. Vì vậy sau khi delete, cần gán d = NULL; NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 75 Hàm trả về kiểu mảng • Ta không được phép trả về kiểu mảng trong hàm. Ví dụ: int[] someFunction(); // Không hợp lệ! • Có thể thay bằng trả về con trỏ tới mảng có cùng kiểu cơ sở: int* someFunction(); // Hợp lệ! NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 76 3. Mảng động 2 chiều • Là mảng của mảng • Sử dụng định nghĩa kiểu con trỏ giúp hiểu rõ hơn: typedef int* IntArrayPtr; IntArrayPtr *m = new IntArrayPtr[3];  Tạo ra mảng 3 con trỏ  Sau đó biến mỗi con trỏ này thành mảng 4 biến int for (int i = 0; i < 3; i++) m[i] = new int[4];  Kết quả là mảng động 3 x 4 NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 77 Lời giải int main() { int row=2, col=3; // Cấp phát vùng nhớ int **p = new int*[row]; if (p == NULL) exit(1); for (int i = 0; i < row; i++) { p[i] = new int[col]; if (p[i] == NULL) exit(1); } // Giải phóng vùng nhớ for (int i = 0; i < row; i++) { delete[] p[i]; } delete[] p; return 0; } NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 78 4. Con trỏ và hàm số • Tham số của hàm là 1 biến con trỏ • Trường hợp thay đổi giá trị của đối số NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 79 void Hoanvi(int *x, int *y) { int z = *x; *x=*y; *y=z; } void main() { int a=1,b=2; cout<<a<<b; // 1 2 Hoanvi(&a,&b); cout<<a<<b; // 2 1 } 4. Con trỏ và hàm số • Tham số của hàm là 1 biến con trỏ • Trường hợp không thay đổi giá trị của đối số NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 80 void Capphat(int *a) { a = new int[5]; for(int i=0; i<5; i++) { a[i]=i+1; cout<< a[i]; } } void main() { int n=5; int *b = &n; cout<<*b; // 5 Capphat(b); // 1 2 3 4 5 cout<<*b; // 5 } 4. Con trỏ và hàm số • Kiểu trả về của hàm là 1 con trỏ NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 81 int* GetArray() { int a = new int [5]; for(int i=0; i<5; i++) a[i]=i+1; return a; } 4. Con trỏ và cấu trúc • Truy xuất các các thuộc tính dùng con trỏ: tên_biến_con_trỏ  tên_thuộc_tính • Ví dụ cấu trúc phân số NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 82 struct PhanSo { int TuSo; int MauSo; }; PhanSo x; x.Tuso=1; x.MauSo=2; PhanSo *p, *q; p = &x; PTuso=3; p MauSo=4; // x(3,4) q = new PhanSo(); q->TuSo = 1; // giống (*q).TuSo=1 q->MauSo = 2;// giống (*q).MauSo=2 4. Con trỏ và cấu trúc • Cấu trúc đệ quy (tự trỏ) struct PERSON { char hoten[30]; struct PERSON *father, *mother; }; struct NODE { int value; struct NODE *pNext; }; Bài tập • Bài 1: Tại sao cần phải giải phóng khối nhớ được cấp phát động?  Khối nhớ không tự giải phóng sau khi sử dụng nên sẽ làm giảm tốc độ thực hiện chương trình hoặc tràn bộ nhớ nếu tiếp tục cấp phát • Bài 2: Điều gì xảy ra nếu ta nối thêm một số ký tự vào một chuỗi (được cấp phát động trước đó) mà không cấp phát lại bộ nhớ cho nó?  Nếu chuỗi đủ lớn để chứa thêm thông tin thì không cần cấp phát lại. Ngược lại phải cấp phát lại để có thêm vùng nhớ. NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 84 Bài tập • Bài 3: Ta thường dùng phép ép kiểu trong những trường hợp nào?  Lấy phần nguyên của số thực hoặc lấy phần thực của phép chia hai số nguyên, • Bài 4: Giả sử c kiểu char, i kiểu int, l kiểu long. Hãy xác định kiểu của các biểu thức sau: • (c + i + l) • (i + ‘A’) • (i + 32.0) • (100 + 1.0) NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 85 Bài tập • Bài 5: Việc cấp phát động nghĩa là gì?  Bộ nhớ được cấp phát động là bộ nhớ được cấp phát trong khi chạy chương trình và có thể thay đổi độ lớn vùng nhớ. • Bài 6: Cho biết sự khác nhau giữa malloc và calloc?  malloc: cấp phát bố nhớ cho một đối tượng.  calloc: cấp phát bộ nhớ cho một nhóm đối tượng. NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 86 Bài tập • Bài 7: Viết câu lệnh sử dụng hàm malloc để cấp phát 1000 số kiểu long.  long *ptr;  ptr = (long *)malloc(1000 * sizeof(long)); • Bài 8: Giống bài 7 nhưng dùng calloc  long *ptr;  ptr = (long *)calloc(1000, sizeof(long));  ptr = (long *)calloc(sizeof(long), 1000); !!! NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 87 Bài tập • Bài 9: Kiểm tra kết quả • Bài 10: Kiểm tra lỗi NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 88 void func() { int n1 = 100, n2 = 3; float ketqua = n1 / n2; printf("%d / %d = %f", n1, n2, ketqua); } int main() { void *p; p = (float *)malloc(sizeof(float)); *p = 1.23; } Bài tập • Bài 11: Cho biết kết quả chương trình sau. Giải thích tại sao ta có được kết quả này. NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 89 void hamf(int*a) { a= new int[5]; for(int i=0; i<5; i++) a[i]=i+1; } void main() { int n=5; int *a= &n; cout<<‚Giá trị *a = "<<*a; hamf(a); cout<<‚Giá trị *a = "<<*a; } Bài tập • Bài 12: Cho biết kết quả chương trình sau. Giải thích tại sao ta có được kết quả này. NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 90 void hamf(int*&a) { a= new int[5]; for(int i=0; i<5; i++) a[i]=i+1; } void main() { int n=5; int *a= &n; cout<<‚Giá trị *a = "<<*a; hamf(a); cout<<‚Giá trị *a = "<<*a; } Bài tập bắt buộc (1/2) 1. Cho biết ý nghĩa của các khai báo và câu lệnh; Tìm lỗi sai trong đoạn code và giải thích (t.t)  xem các bài tập từ 1 đến 12 trong phần trước. 2. Viết chương trình nhập một dãy số hữu tỉ tùy ý (sử dụng con trỏ và sự cấp phát động), xuất ra dãy gồm tất cả các số nhỏ hơn 1 có trong dãy được nhập vào, tính tổng và tích của dãy số hữu tỉ. 3. Viết chương trình khai báo mảng hai chiều có 12x12 phần tử kiểu char. Gán ký tự „X‟ cho mọi phần tử của mảng này. Sử dụng con trỏ đến mảng để in giá trị các phần tử mảng lên màn hình ở dạng lưới. NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 91 Bài tập bắt buộc (2/2) 4. Viết chương trình khai báo mảng 10 con trỏ đến kiểu float, nhận 10 số thực từ bàn phím, sắp xếp lại và in ra màn hình dãy số đã sắp xếp. 5. Chương trình cho phép người dùng nhập các dòng văn bản từ bàn phím đến khi nhập một dòng trống. Chương trình sẽ sắp xếp các dòng theo thứ tự alphabet rồi hiển thị chúng ra màn hình. 6. Làm lại các bài tập về ma trận dùng con trỏ NMLT - CON TRỎ VÀ CẤP PHÁT ĐỘNG 92 Chúc các em học tốt!

Các file đính kèm theo tài liệu này:

  • pdfnhap_mon_lap_trinh_bai_11_con_tro_2582_2054390.pdf
Tài liệu liên quan