Bài giảng Kỹ thuật lập trình - Bài 7: Kiểu con trỏ - Ngô Hữu Dũng

pdf 30 trang ngocly 2880
Bạn đang xem 20 trang mẫu của tài liệu "Bài giảng Kỹ thuật lập trình - Bài 7: Kiểu con trỏ - Ngô Hữu Dũng", để 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_ky_thuat_lap_trinh_bai_7_kieu_con_tro_ngo_huu_dung.pdf

Nội dung text: Bài giảng Kỹ thuật lập trình - Bài 7: Kiểu con trỏ - Ngô Hữu Dũng

  1. Kỹ thuật lập trình Bài 7 – Kiểu con trỏ TS. Ngô Hữu Dũng 181 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  2. Khái niệm con trỏ (pointer)  Con trỏ là biến mà giá trị của nó là địa chỉ bộ nhớ  Địa chỉ bộ nhớ?  scanf(“%d”,&i); // &i là địa chỉ bộ nhớ của biến i  Khai báo  type * variable_name; p = 0073FB60 i = 20  Con trỏ lưu địa chỉ bộ nhớ Biến Địa chỉ Giá trị i 20  int i = 20; 0073FB60 p 0073FB54  int * p; // Khai báo con trỏ p 0073FB60  p = &i; // Con trỏ p được gán bằng địa chỉ của biến i  Ta nói con trỏ p “trỏ vào” biến i 182 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  3. Con trỏ và địa chỉ 1. int x; // Biến số nguyên x 2. int *p; // Con trỏ p kiểu số nguyên 3. p = &x; // p trỏ vào x 4. x = 20; 5. printf("%d ", x); // Giá trị của x 6. printf("%d ", *p); // Giá trị của x 7. printf("%p ", &x); // Địa chỉ của x 8. printf("%p ", p); // Địa chỉ của x 9. *p = 40; 10.printf("Gia tri: %d = %d" , *p, x); 11.printf("Dia chi: %p = %p" , p, &x); 183 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  4. Kiểu con trỏ  Khai báo: Dùng dấu *  int *p;  Địa chỉ của biến mà con trỏ trỏ vào  p = &x  Giá trị của biến mà con trỏ trỏ vào: Dùng dấu *  *p = x  In ra màn hình địa chỉ: Dùng %p  printf(“%p = %p”, p, &x);  Chú ý: Phân biệt các dấu * và các dấu & 184 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  5. Sử dụng con trỏ  Chưa trỏ vào biến  int *p;  *p = 40; // Run-time error!  Trỏ vào biến  int x, *p1, *p2;  p1 = &x; // Trỏ p1 vào biến x  *p1 = 40; // x = 40;  p2 = p1; // gán trực tiếp, tương đương với p2 = &x;  *p2 = 50; // x = 50;  *p1 = 60; // x = 60; 185 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  6. Ví dụ vận dụng 1. int x, y, *px, *py=NULL; 2. px = &x; // px trỏ vào x 3. py = &y; // py trỏ vào y 4. *px = 10; 5. y = 15; 6. printf("x=%d\ny=%d\n",*px,*py); 7. (*px)++; ++*px; 8. *py += 5; 9. printf("x=%d\ny=%d\n",*px,*py); 10. printf("%d+%d=%d\n",x,y,*px + *py); 11. printf("%d*%d=%d\n",x,y,*px * *py); 12. printf("&x=%p\n&y=%p\n",px,py); 186 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  7. Phạm vi ứng dụng con trỏ  Con trỏ có thể trỏ vào bất kỳ dữ liệu nào  Một kiểu nguyên thủy: int, float, char  NULL (rỗng, không trỏ đến đâu)  Một hằng số  Một đối số của hàm  Một mảng/chuỗi  Một cấu trúc struct  Một hàm  Một con trỏ khác (con trỏ trỏ vào con trỏ)  Một ô nhớ cấp phát động  Một tập tin 187 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  8. Lợi hại của con trỏ  Có thể dùng để thao tác với bất kỳ loại dữ liệu nào  Thuật lợi để truy cập đến cấu trúc dữ liệu lớn  Sử dụng bộ nhớ linh hoạt  Cấp phát / giải phóng bộ nhớ động trong quá trình thực thi  Cấp phát bộ nhớ mới: Hàm malloc trong stdlib.h  Giải phóng bộ nhớ: Hàm free trong stdlib.h  Nếu sử dụng con trỏ không cẩn thận  Dễ nhầm lẫn  Khó tìm lỗi 188 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  9. Kiểu nguyên thủy 1. // Con trỏ có thể trỏ vào các kiểu nguyên thủy 2. int i = 10, *pi; 3. float f = 1.2e2, *pf; 4. char c = 'r', *pc; 5. pi = &i; 6. pf = &f; 7. pc = &c; 8. printf("%d,%f,%c\n",*pi,*pf,*pc); 9. // Chú ý dùng đúng kiểu dữ liệu 10.pc = &f; // Warning! 11.pf = &i; // Warning! 12.pi = &c; // Warning! 189 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  10. Con trỏ rỗng - NULL  NULL là giá trị đặc biệt của con trỏ  Không trỏ đến đâu  #define NULL 0  Được định nghĩa trong thư viện stdio.h  Sử dụng  Khởi tạo con trỏ: int *p = NULL;  Kiểm tra một con trỏ  if (p != NULL) printf(“%d”,*p);  if (p) printf(“%d”, *p);  Kết thúc một danh sách dữ liệu 190 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  11. Con trỏ và hằng số  Con trỏ trỏ vào hằng số  int m, n;  const int *ip = &m;  ip = &n; // OK! Có thể thay đổi giá trị con trỏ  *ip = 10; // ERROR !!! Không thể thay đổi giá trị của biến  Con trỏ là hằng số: Chỉ trỏ vào một biến  int m, n;  int * const cip = &m;  *cip = 10; // OK! Có thể thay đổi giá trị của biến  cip = &n; // ERROR !!! Không thể thay đổi giá trị con trỏ 191 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  12. Con trỏ và đối số của hàm 1. void swap (int *px, int *py) // Tham biến 2. { 3. int temp = *px; // Dùng dấu * 4. *px = *py; // để truy cập giá trị 5. *py = temp; 6. } 7. int a = 5, b = 7; 8. swap(&a, &b); // Truyền địa chỉ của biến 9. printf("a = %d, b = %d", a, b); 192 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  13. Con trỏ và mảng  Con trỏ có thể trỏ vào một mảng  #define N 7 pa  int a[N], b[N];  int *pa, *pb; a[0] a[1] a[2] a[3] a[4] a[5] a[6]  pa = a;  pb = &b[0]; pb pc  int *pc; b[0] b[1] b[2] b[3] b[4] b[5] b[6]  pc = &b[5];  pa = a; // Mặc định trỏ vào phần tử đầu tiên của mảng, a[0] 193 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  14. Con trỏ và phần tử của mảng  Địa chỉ của phần tử thứ i  pa + i = &a[i] pa pa+1  Giá trị của phần tử thứ i  *(pa + i) = pa[i] = a[i] a[0] a[1] a[2] a[3] a[4] a[5] a[6]  Ví dụ 1 2 3  *pa = 1; pb pc-1 pc  *(pa+1) = 2;  pa[2] = 3; b[0] b[1] b[2] b[3] b[4] b[5] b[6]  *pb = 4; 4 5 6  *(pc-1) = 5;  *pc = 6; 194 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  15. Ví dụ vận dụng 1. #define N 10 2. int a[N]; 3. int i, *pa, *pt; 4. pa = a; //Trỏ vào mảng, không dùng & 5. for (i=0;i<N;i++) 6. *(pa+i)=i; // *(pa+i)=a[i] 7. for (pt=pa;pt<pa+N;pt++) 8. printf("%d ",*pt); // *pt=a[i] 9. for (i=0;i<N;i++) 10. printf("%d ",pa[i]); // pa[i]=a[i] 195 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  16. Mảng là con trỏ hằng số (constant pointer)  Mảng là con trỏ hằng số  int a[10];  int *pa;  pa = a; // a là con trỏ, pa có thể được gán cho a, không cần &a  pa++; // Con trỏ pa có thể thay đổi giá trị  a = pa; // ERROR! a là hằng số  a++; // ERROR! a là hằng số  Mảng a là con trỏ trỏ vào phần tử đầu tiên của mảng và không thể thay đổi giá trị địa chỉ 196 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  17. Truyền đối số là mảng / chuỗi  Truyền đối số là một mảng vào một hàm  void sapxep(int a[], int n)  Địa chỉ của phần tử đầu tiên của mảng được truyền: &a[0]  Có thể thay thế bằng con trỏ  void sapxep(int *a, int n)  Tương tự với chuỗi  Tham số function(char s[]){ } và function(char *s){ } là tương đương 197 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  18. Ví dụ truyền đối số là mảng/chuỗi 1. int printArray(int *pa, int n) 2. { 3. int *pt; 4. for (pt=pa;pt<pa+n;pt++) //gán, so sánh, tăng 5. printf("%d ",*pt); 6. } 7. int stringLength(char *s) 8. { 9. if(*s=='\0') 10. return 0; 11. else 12. return 1 + stringLength(++s); 13. } 198 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  19. Các phép toán của con trỏ  Phép gán hai con trỏ cùng kiểu dữ liệu  int a, *pa, *pb; pa=&a; pb = pa;  Cộng/trừ con trỏ với số nguyên  Tăng/giảm giá trị của con trỏ theo sizeof()  Ví dụ: pb+n tăng giá trị của p lên n x sizeof(int)  So sánh giữa hai con trỏ trỏ vào phần tử trong cùng một mảng  Các phép toán ==, !=, >, =, <=  Phép trừ giữa hai con trỏ trỏ vào phần tử trong cùng một mảng  Gán hoặc so sánh với NULL (zero) 199 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  20. Ví dụ về các phép toán của con trỏ 1. int printRevertArray(int *pa, int n) 2. { 3. int *pt = pa+n-1; // Cộng số tự nhiên 4. while (pt>=pa) // Phép so sánh 5. { 6. printf("%d ",*pt); pt ; // Phép toán giảm 7. } 8. } 9. int stringLength(char *s) 10. { 11. char *p = s; // Phép gán 12. while (*p!='\0') 13. p++; // Phép toán tăng 14. return p - s; // Phép trừ của con trỏ 15. } 200 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  21. Con trỏ và chuỗi  Tương tự như với mảng nhưng chuỗi có một đặc điểm khác  Chuỗi kết thúc bằng ký tự rỗng, ‘\0’ 1. void stringCopy(char *s, char *t) 2. { 3. while (*t != '\0') 4. { 5. *s = *t; 6. s++; 7. t++; 8. } 9. *s = '\0'; // Ký tự rỗng kết thúc chuỗi 10. } 201 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  22. Con trỏ và chuỗi (tt) 1. void stringCopy1(char *s, char *t) 2. { 3. while ((*s = *t) != '\0'){ //Gán, so sánh 4. s++; t++; // Tăng 5. } 6. } 7. void stringCopy2(char *s, char *t) 8. { 9. while ((*s++ = *t++) != '\0');//Gán, so sánh, tăng 10. } 11. void stringCopy3(char *s, char *t) 12. { 13. while (*s++ = *t++); // Gán, tăng 14. } 202 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  23. Con trỏ và cấu trúc 1. struct t_date{ 2. int day; 3. int month; 4. int year; 5. }; 6. struct t_date today; // Biến cấu trúc 7. struct t_date *ptr; // Con trỏ kiểu cấu trúc 8. ptr = &today; // Trỏ vào cấu trúc 9. (*ptr).day = 15; // Truy cập member, cách 1 10. (*ptr).month = 9; // Dùng dấu ‘.’ 11. (*ptr).year = 2016; 12. ptr->day = 15; // Truy cập member, cách 2 13. ptr->month = 9; // Dùng dấu ‘->’ 14. ptr->year = 2016; 203 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  24. Con trỏ và cấu trúc (2) 1. struct t_student{ 2. int ID; 3. char *ten; 4. struct t_date birth, *pB; 5. }; 6. struct t_student SV, *pSV; 7. pSV = &SV; // Trỏ vào cấu trúc t_stu 8. pSV->ID = 123; // SV.ID 9. pSV->ten = "Nguyen Van A";// SV.ten 10. pSV->birth.day = 16; // SV.birth.day 11. pSV->pB = &SV.birth; // Trỏ vào cấu trúc t_date 12. pSV->pB->month = 4; // SV.birth.month 13. (*pSV->pB).year = 1996; // SV.birth.year 204 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  25. Con trỏ và cấu trúc (3) 1. ++pSV->ID; // Tăng giá trị ID 2. printf("%d ",pSV->ID); 3. printf("%c",*pSV->ten); //Ký tự đầu tiên ‘N’ 4. printf("%c",pSV->ten[1]); //Ký tự thứ 2 ‘g’ 5. printf("%s ",pSV->ten+2); //Từ ký tự ‘u’->hết 6. printf("%d ",++pSV->pB->day); //Tăng day 7. printf("%d ",(*pSV->pB).month); 8. printf("%d\n",SV.pB->year); 9. while(*pSV->ten!='\0') 10. printf("%c",*pSV->ten++); 11.printf("\n%s",pSV->ten); // !? Chuỗi rỗng !? 12.printf("%s", pSV->ten); // !? 205 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  26. Con trỏ và hàm  Hàm có thể trả về kiểu con trỏ  Đối số kiểu con trỏ 1. char * stringCopy4(char *s, char *t) 2. { 3. int count = 0; 4. do 5. count++; 6. while (*s++ = *t++); 7. return s-count; 8. } 206 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  27. Con trỏ đến con trỏ  Con trỏ có thể chứa địa chỉ của con trỏ khác  char x; x x x  char * y;  char z; 'x' 00000078 00AFF75F  x = 'x'; 00000078 00AFF75F 00AFF750  y = &x;  z = &y; 207 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  28. Con trỏ và cấp phát động  Con trỏ có thể được cấp phát bộ nhớ  Trỏ trực tiếp vào bộ nhớ động  Không cần thông qua một biến cụ thể 1. #include 2. int n ,*a , i; 3. scanf("%d", &n); 4. a =(int*)malloc(sizeof(int)*n); // Cấp phát bộ nhớ 5. for(i=0;i<n;i++) 6. a[i]=i; 7. for (i=0;i<n;i++) 8. printf("%d\n",a[i]); 9. free(a); // Giải phóng bộ nhớ 208 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  29. Con trỏ và tập tin  Con trỏ có thể trỏ vào tập tin 1. #include < 2. FILE * pFile; 3. pFile = fopen ("myfile.txt","w"); 4. if (pFile!=NULL) 5. { 6. fputs ("fopen example",pFile); 7. fclose (pFile); 8. } 9. return 0; 209 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng
  30. Hết bài 7  Khái niệm con trỏ  Khái quát các ứng dụng của con trỏ  Kiểu nguyên thủy: int, float, char  NULL (rỗng, không trỏ đến đâu)  Hằng số  Đối số của hàm  Mảng/chuỗi  Cấu trúc struct  Hàm  Con trỏ trỏ vào con trỏ  Cấp phát động  Tập tin 210 Kỹ thuật lập trình | DHTH11C | HK1 | 2016-2017 Ngô Hữu Dũng