Bài giảng Phương pháp lập trình - Bài 10: Kiểu con trỏ

pdf 80 trang phuongnguyen 3441
Bạn đang xem 20 trang mẫu của tài liệu "Bài giảng Phương pháp lập trình - Bài 10: Kiểu con trỏ", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên

Tài liệu đính kèm:

  • pdfbai_giang_phuong_phap_lap_trinh_bai_10_kieu_con_tro.pdf

Nội dung text: Bài giảng Phương pháp lập trình - Bài 10: Kiểu con trỏ

  1. TRƯỜNG ĐẠI HỌC CÔNG NGHIỆP THÀNH PHỐ HỒ CHÍ MINH Phương pháp lập trình Kiểu con trỏ TS. Ngô Hữu Dũng
  2. Kiến trúc máy tí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.  RAM dùng để chứa một phần hệ điều hành, các lệnh chương trình, các dữ liệu  Mỗi ô nhớ có địa chỉ duy nhất và địa chỉ này được đánh số từ 0 trở đi.  Ví dụ  RAM 512MB được đánh địa chỉ từ 0 đến 229 – 1  RAM 2GB được đánh địa chỉ từ 0 đến 231 – 1 Phương pháp lập trình - Con trỏ
  3. Khai báo biến trong C  Quy trình xử lý của trình biên dịch  Dành riêng một vùng nhớ với địa chỉ duy nhất để lưu biến đó.  Liên kết địa chỉ ô nhớ đó với tên biến.  Khi gọi tên biến, nó sẽ truy xuất tự động đến ô nhớ đã liên kết với tên biến.  Ví dụ: int a = 0x1234; // Giả sử địa chỉ 0x0B 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 34 12 00 00 a Phương pháp lập trình - Con trỏ
  4. Khái niệm con trỏ  Khái niệm  Địa chỉ của biến là một con số.  Ta có thể tạo biến khác để lưu địa chỉ của biến này Con trỏ. 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 34 12 00 00 0B 00 00 00 a pa Phương pháp lập trình - Con trỏ
  5. 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ụ char *ch1, *ch2; int *p1, p2;  ch1 và ch2 là biến con trỏ, trỏ tới vùng nhớ kiểu char (1 byte).  p1 là biến con trỏ, trỏ tới vùng nhớ kiểu int (4 bytes) còn p2 là biến kiểu int bình thường. Phương pháp lập trình - Con trỏ
  6. Khai báo con trỏ  Sử dụng từ khóa typedef typedef * ; ;  Ví dụ typedef int *pint; int *p1; pint p2, p3;  Lưu ý khi khai báo kiểu dữ liệu mới  Giảm bối rối khi mới tiếp xúc với con trỏ.  Nhưng dễ nhầm lẫn với biến thường. Phương pháp lập trình - Con trỏ
  7. Con trỏ NULL  Khái niệm  Con trỏ NULL là con trỏ không trỏ và đâu cả.  Khác với con trỏ chưa được khởi tạo. int n; int *p1 = &n; int *p2; // unreferenced local varialbe int *p3 = NULL; NULL Phương pháp lập trình - Con trỏ
  8. Khởi tạo kiểu con trỏ  Khởi tạo  Khi mới khai báo, biến con trỏ được đặt ở địa chỉ nào đó (không biết trước). chứa giá trị không xác định trỏ đến vùng nhớ không biết trước.  Đặt địa chỉ của biến vào con trỏ (toán tử &)  Ví dụ = & ; int a, b; int *pa = &a, *pb; pb = &b; Phương pháp lập trình - Con trỏ
  9. Sử dụng con trỏ  Truy xuất đến ô nhớ mà con trỏ trỏ đến  Con trỏ chứa một số nguyên chỉ địa chỉ.  Vùng nhớ mà nó trỏ đến, sử dụng toán tử *.  Ví dụ int a = 5, *pa = &a; printf(“%d\n”, pa); // Giá trị biến pa printf(“%d\n”, *pa); // Giá trị vùng nhớ pa trỏ đến printf(“%d\n”, &pa); // Địa chỉ biến pa 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 05 00 00 00 0B 00 00 00 a pa Phương pháp lập trình - Con trỏ
  10. Kích thước của con trỏ  Kích thước của con trỏ char *p1; int *p2; float *p3; double *p4;  Con trỏ chỉ lưu địa chỉ nên kích thước của mọi con trỏ là như nhau:  Môi trường MD-DOS (16 bit): 2 bytes  Môi trường Windows (32 bit): 4 bytes Phương pháp lập trình - Con trỏ
  11. Các cách truyền đối số  Truyền giá trị (tham trị) 1. #include 2. void hoanvi(int x, int y); 3. void main() 4. { 5. int a = 5; b = 6; 6. hoanvi(a, b); 7. printf(“a = %d, b = %d”, a, b); 8. } 9. void hoanvi(int x, int y) 10.{ 11. int t = x; x = y; y = t; 12.} Phương pháp lập trình - Con trỏ
  12. Truyền giá trị (tham trị) int x int y 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 05 00 00 00 06 00 00 00 int t = x; hoanvi int x int y x = y; y = t; 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 05 00 00 00 06 00 00 00 int a = 5 int b = 6 Phương pháp lập trình - Con trỏ
  13. Các cách truyền đối số  Truyền địa chỉ (con trỏ) 1. #include 2. void hoanvi(int *x, int *y); 3. void main() 4. { 5. int a = 2912; b = 1706; 6. hoanvi(&a, &b); 7. printf(“a = %d, b = %d”, a, b); 8. } 9. void hoanvi(int *x, int *y) 10.{ 11. int t = *x; *x = *y; *y = t; 12.} Phương pháp lập trình - Con trỏ
  14. Truyền địa chỉ (con trỏ) int *x int *y 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 0B 00 00 00 0F 00 00 00 int t = *x; hoanvi int *x int *y *x = *y; *y = *t; 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 05 00 00 00 06 00 00 00 int a = 5 int b = 6 Phương pháp lập trình - Con trỏ
  15. Các cách truyền đối số  Truyền tham chiếu (C++) 1. #include 2. void hoanvi(int &x, int &y); 3. void main() 4. { 5. int a = 2912; b = 1706; 6. hoanvi(a, b); 7. printf(“a = %d, b = %d”, a, b); 8. } 9. void hoanvi(int &x, int &y) 10.{ 11. int t = x; x = y; y = t; 12.} Phương pháp lập trình - Con trỏ
  16. Truyền tham chiếu (C++) int t = x; hoanvi int &x int &y x = y; y = t; 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 05 00 00 00 06 00 00 00 int a = 5 int b = 6 Phương pháp lập trình - Con trỏ
  17. Một số lưu ý  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; Phương pháp lập trình - Con trỏ
  18. Con trỏ và mảng một chiều  Mảng một chiều int array[3];  Tên mảng array là một hằng con trỏ không thể thay đổi giá trị của hằng này.  array là địa chỉ đầu tiên của mảng array == &array[0] 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 array Phương pháp lập trình - Con trỏ
  19. Con trỏ và mảng một chiều  Con trỏ đến mảng một chiều 1. int array[3], *parray; 2. parray = array; // Cách 1 3. parray = &array[0]; // Cách 2 18 19 1A 1B 1C 1D 1E 1F 0B 00 00 00 parray 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 array Phương pháp lập trình - Con trỏ
  20. Phép toán số học trên con trỏ  Phép cộng (tăng)  + n  + n * sizeof( )  Có thể sử dụng toán tử gộp += hoặc ++ p = array +2 +1 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 int array[3]; Phương pháp lập trình - Con trỏ
  21. Phép toán số học trên con trỏ  Phép trừ (giảm)  – n  – n * sizeof( )  Có thể sử dụng toán tử gộp –= hoặc – – p = &array[2] –2 –1 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 int array[3]; Phương pháp lập trình - Con trỏ
  22. 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) p1 = array p2 = &array[2] p1 – p2= (0B – 13)/sizeof(int) = –2 p2 – p1= (13 – 0B)/sizeof(int) = +2 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 int array[3]; Phương pháp lập trình - Con trỏ
  23. Phép toán số học trên con trỏ  Các phép toán khác  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: * / % Phương pháp lập trình - Con trỏ
  24. Con trỏ và mảng một chiều  Truy xuất đến phần tử thứ n của mảng (không sử dụng biến mảng)  array[n] == p[n] == *(p + n) * ( p + 2 ) 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 int array[3]; Phương pháp lập trình - Con trỏ
  25. Con trỏ và mảng một chiều  Ví dụ nhập mảng void main() { int a[10], n = 10, *pa; pa = a; // hoặc pa = &a[0]; for (int i = 0; i<n; i++) scanf(“%d”, &a[i]); scanf(“%d”, &p[i]); scanf(“%d”, a + i); scanf(“%d”, p + i); scanf(“%d”, a++); scanf(“%d”, p++); } &a[i]  (a + i)  (p + i)  &p[i] Phương pháp lập trình - Con trỏ
  26. Con trỏ và mảng một chiều  Ví dụ xuất mảng void main() { int a[10], n = 10, *pa; pa = a; // hoặc pa = &a[0]; for (int i = 0; i<n; i++) printf(“%d”, a[i]); printf(“%d”, p[i]); printf(“%d”, *(a + i)); printf(“%d”, *(p + i)); printf(“%d”, *(a++)); printf(“%d”, *(p++)); } a[i]  *(a + i)  *(p + i)  p[i] Phương pháp lập trình - Con trỏ
  27. Truyền mảng 1 chiều cho hàm  Chú ý!  Mảng một chiều truyền cho hàm là địa chỉ của phần tử đầu tiên chứ không phải toàn mảng. xuất int a[3] int n int a[] int *a 10 11 13 14 15 16 17 18 19 20 21 22 23 24 int array[3]; Phương pháp lập trình - Con trỏ
  28. Con trỏ và mảng một chiều  Ví dụ void xuat(int a[10], int n) { for (int i = 0; i<n; i++) printf(“%d”, *(a++)); // OK } void main() { int a[10], n = 10; for (int i = 0; i<n; i++) printf(“%d”, *(a++)); // Lỗi } Đối số mảng truyền cho hàm không phải hằng con trỏ. Phương pháp lập trình - Con trỏ
  29. Con trỏ và mảng một chiều  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ó.  Đối số mảng một chiều truyền cho hàm là địa chỉ phần tử đầu tiên của mảng. Phương pháp lập trình - Con trỏ
  30. Con trỏ cấu trúc  Truy xuất bằng 2 cách -> (* ).  Ví dụ struct PHANSO { int tu, mau; }; PHANSO ps1, *ps2 = &p1; // ps2 là con trỏ ps1.tu = 1; ps1.mau = 2; ps2->tu = 1; ps2->mau = 2; (*ps2).tu = 1; (*ps2).mau = 2; Phương pháp lập trình - Con trỏ
  31. Con trỏ cấu trúc  Gán hai cấu trúc struct PHANSO { int tu, mau; }; PHANSO ps1, *ps2; ps1.tu = 1; ps1.mau = 2; // ps1 = 1/2 ps2 = &ps1; ps2->tu = 3; ps2->mau = 4; // ps1 = 3/4 Phương pháp lập trình - Con trỏ
  32. Bài tập lý thuyết  Bài 1: 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 Phương pháp lập trình - Con trỏ
  33. Bài tập lý thuyết  Bài 2: Tìm lỗi #include #include void main() { int *x, y = 2; *x = y; *x += y++; printf("%d %d",*x,y); getch(); } Phương pháp lập trình - Con trỏ
  34. Bài tập 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: Các phần tử trong mảng được sắp xếp trong bộ nhớ như thế nào?  Bài 5: 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. Phương pháp lập trình - Con trỏ
  35. Bài tập lý thuyết  Bài 6: Nếu ta truyền cho hàm đối số là mảng một chiều. Trình bày hai cách nhận biết phần tử cuối của mảng?  Bài 7: Trình bày 6 phép toán có thể thực hiện trên con trỏ?  Bài 8: 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. p2 – p1 = ?  Bài 9: Giống như câu trên nhưng đối với mảng float? Phương pháp lập trình - Con trỏ
  36. Bài tập  Bài 10: Trình bày khai báo con trỏ pchar trỏ đến kiểu char.  Bài 11: Cho biến cost kiểu int. Khai báo và khởi tạo con trỏ pcost trỏ đến biến này.  Bài 12: Gán giá trị 100 cho biến cost sử dụng hai cách trực tiếp và gián tiếp.  Bài 13: In giá trị của con trỏ và giá trị của biến mà nó trỏ tới.  Bài 14: Sử dụng con trỏ để làm lại các bài tập về mảng một chiều. Phương pháp lập trình - Con trỏ
  37. Bài tập lý thuyết  Bài 15: Cho đoạn chương trình sau: int *pint; float a; char c; double *pd; Hãy chọn phát biểu sai cú pháp: a. a = *pint; b. c = *pd; c. *pint = *pd; d. pd = a; Phương pháp lập trình - Con trỏ
  38. Bài tập thực hành  Bài 16: Viết chương trình nhập số nguyên dương n gồm k chữ số (0 < k ≤ 5) , sắp xếp các chữ số của n theo thứ tự tăng dần. Ví dụ:  Nhập n = 1536  Kết quả sau khi sắp xếp: 1356. Phương pháp lập trình - Con trỏ
  39. Con trỏ cấp 2 (con trỏ đến con trỏ)  Đặt vấn đề void CapPhat(int *p, int n) { p = (int *)malloc(n * sizeof(int)); } void main() { int *a = NULL; CapPhat(a, 2); // a vẫn = NULL } Làm sao thay đổi giá trị của con trỏ (không phải giá trị mà nó trỏ đến) sau khi gọi hàm? Phương pháp lập trình - Con trỏ
  40. Con trỏ cấp 2 int *p int n 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 22N 00U 00L 00L 02 00 00 00 CapPhat int *p int n NULL 2 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 N U L L int *a = NULL Phương pháp lập trình - Con trỏ
  41. Con trỏ cấp 2  Giải pháp  Sử dụng tham chiếu int *&p (trong C++) void CapPhat(int *&p, int n) { p = (int *)malloc(n * sizeof(int)); }  Không thay đổi trực tiếp tham số mà trả về int* CapPhat(int n) { int *p = (int *)malloc(n * sizeof(int)); return p; } Phương pháp lập trình - Con trỏ
  42. Con trỏ cấp 2  Giải pháp  Sử dụng con trỏ p trỏ đến con trỏ a này. Hàm sẽ thay đổi giá trị của con trỏ â gián tiếp thông qua con trỏ p. void CapPhat(int p, int n) { *p = (int *)malloc(n * sizeof(int)); } void main() { int *a = NULL; CapPhat(&a, 4); } Phương pháp lập trình - Con trỏ
  43. Con trỏ cấp 2 int p int n 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 0B 00 00 00 02 00 00 00 CapPhat int p int n 0B 2 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 22N 00U 00L 00L int *a = NULL Phương pháp lập trình - Con trỏ
  44. Con trỏ cấp 2  Lưu ý int x = 12; int *ptr = &x; // OK int k = &x; ptr = k; // Lỗi int ptr_to_ptr = &ptr; // OK int ptr_to_ptr = &x; // Lỗi ptr_to_ptr = 12; // OK *ptr_to_ptr = 12; // Lỗi printf(“%d”, ptr_to_ptr); // Địa chỉ ptr printf(“%d”, *ptr_to_ptr); // Giá trị ptr printf(“%d”, ptr_to_ptr); // Giá trị x Phương pháp lập trình - Con trỏ
  45. Con trỏ và mảng 2 chiều int a[3][4]; 0 1 2 3 4 5 6 7 8 9 10 11 a 0 1 2 int 0 1 2 3 1 2 a 0 1 2 int[4] Phương pháp lập trình - Con trỏ
  46. Con trỏ và mảng 2 chiều  Hướng tiếp cận 1  Các phần tử tạo thành mảng 1 chiều  Sử dụng con trỏ int * để duyệt mảng 1 chiều int *p = (int *)a +1 0 1 2 3 4 5 6 7 8 9 10 11 int a[3][4] Phương pháp lập trình - Con trỏ
  47. Hướng tiếp cận 1  Nhập / Xuất theo chỉ số mảng 1 chiều #define D 3 #define C 4 void main() { int a[D][C], i; int *p = (int *)a; for (i = 0; i < D*C; i++) { printf(“Nhap phan tu thu %d: ”, i); scanf(“%d”, p + i); } for (i = 0; i < D*C; i++) printf(“%d ”, *(p + i)); } Phương pháp lập trình - Con trỏ
  48. Hướng tiếp cận 1  Liên hệ giữa chỉ số mảng 1 chiều và chỉ số mảng 2 chiều (d, c) i ? i = d*C + c 0 1 2 3 4 5 6 7 8 9 10 11 aCxD 0 1 i (d, c) ? 2 d = i / C c = i % C Phương pháp lập trình - Con trỏ
  49. Hướng tiếp cận 1  Nhập / Xuất theo chỉ số mảng 2 chiều int a[D][C], i, d, c; int *p = (int *)a; for (i = 0; i < D*C; i++) { printf(“Nhap a[%d][%d]: ”, i / C, i % C); scanf(“%d”, p + i); } for (d = 0; d < D; d++) { for (c = 0; c < C; c++) printf(“%d ”, *(p + d * C + c));// *p++ printf(“\n”; } Phương pháp lập trình - Con trỏ
  50. Con trỏ và mảng 2 chiều  Hướng tiếp cận 2  Mảng 1 chiều, mỗi phần tử là mảng 1 chiều  a chứa a[0], a[1], a = &a[0]  a[0] chứa a[0][0], a[0][1], a[0] = &a[0][0] a +1 0 1 2 int a[3][4] +1 a[0] Phương pháp lập trình - Con trỏ
  51. Hướng tiếp cận 2  Kích thước của mảng void main() { int a[3][4]; printf(“KT của a = %d”, sizeof(a)); printf(“KT của a[0] = %d”, sizeof(a[0])); printf(“KT của a[0][0] = %d”, sizeof(a[0][0])); } 0 1 2 a 0 1 2 3 a[0] a[0][0] Phương pháp lập trình - Con trỏ
  52. Hướng tiếp cận 2  Nhận xét  a là con trỏ đến a[0], a[0] là con trỏ đến a[0][0] a là con trỏ cấp 2.  Có thể truy xuất a[0][0] bằng 3 cách: void main() { int a[3][4]; a[0][0] = 1; *a[0] = 1; a = 1; a[1][0] = 1; *a[1] = 1; (a+1) = 1; a[1][2] = 1; *(a[1]+2) = 1; *(*(a+1)+2) = 1; } Phương pháp lập trình - Con trỏ
  53. Hướng tiếp cận 2  Truyền mảng cho hàm  Truyền địa chỉ phần tử đầu tiên cho hàm.  Khai báo con trỏ rồi gán địa chỉ mảng cho con trỏ này để nó trỏ đến mảng.  Con trỏ này phải cùng kiểu với biến mảng, tức là con trỏ đến vùng nhớ n phần tử (mảng)  Cú pháp  Ví dụ (* )[ ]; int (*ptr)[4]; Phương pháp lập trình - Con trỏ
  54. Hướng tiếp cận 2  Truyền mảng cho hàm void Xuat_1_Mang_C1(int (*ptr)[4]) // ptr[][4] { int *p = (int *)ptr; for (int i = 0; i a + i } Phương pháp lập trình - Con trỏ
  55. Hướng tiếp cận 2  Truyền mảng cho hàm void Xuat_1_Mang_C2(int *ptr, int n) // ptr[] { for (int i = 0; i < n; i++) printf(“%d ”, *ptr++); } void main() { int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}}; int (*ptr)[4]; ptr = a; for (int i = 0; i < 3; i++) Xuat_1_Mang_C2((int *)ptr++); Xuat_1_Mang_C2((int *)(a + i));// a++ sai } Phương pháp lập trình - Con trỏ
  56. Hướng tiếp cận 2  Truyền mảng cho hàm void Xuat_n_Mang_C1(int (*ptr)[4], int n) { int *p = (int *)ptr; for (int i = 0; i < n * 4; i++) printf(“%d ”, *p++); } void main() { int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}}; int (*ptr)[4]; ptr = a; Xuat_n_Mang_1(ptr, 3); Xuat_n_Mang_1(a, 3); } Phương pháp lập trình - Con trỏ
  57. Hướng tiếp cận 2  Truyền mảng cho hàm void Xuat_n_Mang_C2(int (*ptr)[4], int n) { int *p; for (int i = 0; i < n; i++) { p = (int *)ptr++; for (int i = 0; i < 4; i++) printf(“%d ”, *p++); printf(“\n”); } } Phương pháp lập trình - Con trỏ
  58. Mảng con trỏ  Đặt vấn đề  Sử dụng cấu trúc dữ liệu nào để lưu trữ thông tin sau? 0 1 2 3 4 5 6 7 0 1 5 6 1 2 9 1 2 1 7 0 6 2 0 2  Giải pháp?  Cách 1: Mảng 2 chiều 3x8 (tốn bộ nhớ) Phương pháp lập trình - Con trỏ
  59. Mảng con trỏ  Cách 2: Mảng 1 chiều các con trỏ 18 19 1A 1B 1C 1D 1E 1F 1 5 6 28 29 2A 2B 2C 2D 2E 2F 2 9 1 2 1 7 0 6 3A 3B 3C 0 2 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 19 00 00 00 28 00 00 00 3A 00 00 00 array Phương pháp lập trình - Con trỏ
  60. Mảng con trỏ  Ví dụ void print_strings(char *p[], int n) { for (int i = 0; i<n; i++) printf(“%s ”, p[i]); } void main() { char *message[4] = {“Tin”, “Hoc”, “Co”, “So”}; print_strings(message, 4); } Phương pháp lập trình - Con trỏ
  61. Con trỏ hàm  Khái niệm  Hàm cũng đuợc lưu trữ trong bộ nhớ, tức là cũng có địa chỉ.  Con trỏ hàm là con trỏ trỏ đến vùng nhớ chứa hàm và có thể gọi hàm thông qua con trỏ đó. 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 11 00 00 00 p int Cong(int, int) Phương pháp lập trình - Con trỏ
  62. Con trỏ hàm  Khai báo tường minh (* )(ds tham số);  Ví dụ // Con trỏ đến hàm nhận đối số int, trả về int int (*ptof1)(int x); // Con trỏ đến hàm nhận 2 đối số double, không trả về void (*ptof2)(double x, double y); // Con trỏ đến hàm nhận đối số mảng, trả về char char (*ptof3)(char *p[]); // Con trỏ đến không nhận đối số và không trả về void (*ptof4)(); Phương pháp lập trình - Con trỏ
  63. Con trỏ hàm  Khai báo không tường minh (thông qua kiểu) typedef (* )(ds tham số); ;  Ví dụ int (*pt1)(int, int); // Tường minh typedef int (*PhepToan)(int, int); PhepToan pt2, pt3; // Không tường minh Phương pháp lập trình - Con trỏ
  64. Con trỏ hàm  Gán giá trị cho con trỏ hàm = ; = & ;  Hàm được gán phải cùng dạng (vào, ra)  Ví dụ int Cong(int x, int y); // Hàm int Tru(int x, int y); // Hàm int (*tinhtoan)(int x, int y); // Con trỏ hàm tinhtoan = Cong; // Dạng ngắn gọn tinhtoan = &Tru; // Dạng sử dụng địa chỉ tinhtoan = NULL; // Không trỏ đến đâu cả Phương pháp lập trình - Con trỏ
  65. Con trỏ hàm  So sánh con trỏ hàm if (tinhtoan != NULL) { if (tinhtoan == &Cong) printf(“Con trỏ đến hàm Cong.”); else if (tinhtoan == &Tru) printf(“Con trỏ đến hàm Tru.”); else printf(“Con trỏ đến hàm khác.”); } else printf(“Con trỏ chưa được khởi tạo!”); Phương pháp lập trình - Con trỏ
  66. Con trỏ hàm  Gọi hàm thông qua con trỏ hàm  Sử dụng toán tử lấy nội dung “*” (chính quy) nhưng trường hợp này có thể bỏ int Cong(int x, int y); int Tru(int x, int y); int (*tinhtoan)(int, int); tinhtoan = Cong; int kq1 = (*tinhtoan)(1, 2); // Chính quy int kq2 = tinhtoan(1, 2); // Ngắn gọn Phương pháp lập trình - Con trỏ
  67. Con trỏ hàm  Truyền tham số là con trỏ hàm int Cong(int x, int y); int Tru(int x, int y); int TinhToan(int x, int y, int (*pheptoan)(int, int)) { int kq = (*pheptoan)(x, y); // Gọi hàm return kq; } void main() { int (*pheptoan)(int, int) = &Cong; int kq1 = TinhToan(1, 2, pheptoan); int kq2 = TinhToan(1, 2, &Tru); } Phương pháp lập trình - Con trỏ
  68. Con trỏ hàm  Trả về con trỏ hàm int (*LayPhepToan(char code))(int, int) { if (code == ‘+’) return &Cong; return &Tru; } void main() { int (*pheptoan)(int, int) = NULL; pheptoan = LayPhepToan(‘+’); int kq2 = pheptoan(1, 2, &Tru); } Phương pháp lập trình - Con trỏ
  69. Con trỏ hàm  Trả về con trỏ hàm (khai báo kiểu) typedef (*PhepToan)(int, int); PhepToan LayPhepToan(char code) { if (code == ‘+’) return &Cong; return &Tru; } void main() { PhepToan pheptoan = NULL; pheptoan = LayPhepToan(‘+’); int kq2 = pheptoan(1, 2, &Tru); } Phương pháp lập trình - Con trỏ
  70. Con trỏ hàm  Mảng con trỏ hàm typedef (*PhepToan)(int, int); void main() { int (*array1[2])(int, int); // tường minh PhepToan array2[2]; // kô tường minh array1[0] = array2[1] = &Cong; array1[1] = array2[0] = &Tru; printf(“%d\n”, (*array1[0])(1, 2)); printf(“%d\n”, array1[1](1, 2)); printf(“%d\n”, array2[0](1, 2)); printf(“%d\n”, array2[1](1, 2)); } Phương pháp lập trình - Con trỏ
  71. Con trỏ hàm  Lưu ý  Không được quên dấu () khi khai báo con trỏ hàm  int (*PhepToan)(int x, int y);  int *PhepToan(int x, int y);  Có thể bỏ tên biến tham số trong khai báo con trỏ hàm  int (*PhepToan)(int x, int y);  int (*PhepToan)(int, int); Phương pháp lập trình - Con trỏ
  72. Bài tập lý thuyết  Câu 1: Ta có thể khai báo và sử dụng biến con trỏ đến cấp thứ mấy?  Câu 2: Có sự khác nhau giữa con trỏ đến một chuỗi và con trỏ đến một mảng ký tự không?  Câu 3: Nếu không sử dụng các kiến thức nâng cao về con trỏ, ta có thể giải quyết một số bài toán nào đó không?  Câu 4: Hãy nên một số ứng dụng của con trỏ hàm. Phương pháp lập trình - Con trỏ
  73. Bài tập lý thuyết  Câu 5: Viết đoạn lệnh khai báo biến x kiểu float, khai báo và khởi tạo con trỏ px đến biến x và khai báo và khởi tạo con trỏ ppx đến con trỏ px.  Câu 6: Ta muốn gán 100 cho x thông qua con trỏ ppx bằng biểu thức gán “ppx = 100;” có được không?  Câu 7: Giả sử ta khai báo mảng array 3 chiều int array[2][3][4]. Cho biết cấu trúc của mảng này đối với trình biên dịch C.  Câu 8: Cho biết array[0][0] có nghĩa là gì? Phương pháp lập trình - Con trỏ
  74. Bài tập lý thuyết  Câu 9: Xét xem biểu thức so sánh nào sau đây đúng  array[0][0] == & array[0][0][0];  array[0][1] == array[0][0][1];  array[0][1] == &array[0][1][0];  Câu 10: Viết nguyên mẫu của một hàm nhận một mảng con trỏ đến kiểu char làm đối số, và giá trị trả về có kiểu void.  Câu 11: Theo cách viết của câu 10, ta có thể biết được số phần tử của mảng được truyền kô? Phương pháp lập trình - Con trỏ
  75. Bài tập lý thuyết  Câu 12: Con trỏ đến hàm là gì?  Câu 13: Viết khai báo con trỏ đến một hàm mà hàm đó có giá trị trả về kiểu char, nhận đối số là một mảng con trỏ đến kiểu char.  Câu 14: Ta viết khai báo con trỏ ở câu 12 như vậy có đúng không? char *ptr(char *x[]);  Câu 15: Cho biết ý nghĩa của các khai báo sau:  int *var1;  int var2;  int var3; Phương pháp lập trình - Con trỏ
  76. Bài tập lý thuyết  Câu 16: Cho biết ý nghĩa của các khai báo sau:  int a[3][12];  int (*b)[12];  int *c[12];  Câu 17: Cho biết ý nghĩa của các khai báo sau:  char *z[10];  char *y(int field);  char (*x)(int field); Phương pháp lập trình - Con trỏ
  77. Bài tập lý thuyết  Câu 18: Viết khai báo con trỏ func đến một hàm nhận đối số là một số nguyên và trả về giá trị kiểu float.  Câu 19: Viết khai báo một mảng con trỏ đến hàm. Các hàm nhận một chuỗi ký tự làm tham số và trả về giá trị kiểu nguyên. Ta có thể sử dụng mảng này để làm gì?  Câu 20: Viết câu lệnh khai báo một mảng 10 con trỏ đến kiểu char. Phương pháp lập trình - Con trỏ
  78. Bài tập thực hành  Câu 21: Tìm lỗi sai trong đoạn lệnh sau  int x[3][12];  int *ptr[12];  ptr = x;  Câu 22: 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. Phương pháp lập trình - Con trỏ
  79. Bài tập thực hành  Câu 23: 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.  Câu 24: Sửa lại bài tập 22 để người sử dụng có thể lựa chọn cách sắp xếp theo thứ tự tăng hay giảm dần. Phương pháp lập trình - Con trỏ
  80. Bài tập thực hành  Câu 25: 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.  Câu 26: Sử dụng con trỏ hàm để viết các hàm sắp xếp sau  Tăng dần  Giảm dần  Dương giảm rồi âm tăng, cuối cùng là số 0  Phương pháp lập trình - Con trỏ