Bài giảng Cấu trúc máy tính lập trình hợp ngữ - Con trỏ
Bạn đang xem 20 trang mẫu của tài liệu "Bài giảng Cấu trúc máy tính lập trình hợp ngữ - 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:
- bai_giang_cau_truc_may_tinh_lap_trinh_hop_ngu_con_tro.ppt
Nội dung text: Bài giảng Cấu trúc máy tính lập trình hợp ngữ - Con trỏ
- CHƯƠNG VI:CONTRỎ
- Giới thiệu dữ liệu kiểu con trỏ • Khi khai báo biến : int a; thì a là biến có kích thước và kiểu dữ liệu xác định, gọi là biến tĩnh • Ô nhớ a được cấp phát mà không cần biết đến nó có được sử dụng hết trong chương trình hay không • Các biến tĩnh sẽ tồn tại trong suốt thời gian thực thi chương trình
- Giới thiệu dữ liệu kiểu con trỏ Một số hạn chế có thể gặp phải khi sử dụng các biến tĩnh: • Cấp phát ô nhớ dư, gây ra lãng phí ô nhớ. • Cấp phát ô nhớ thiếu, chương trình thực thi bị lỗi.
- Giới thiệu dữ liệu kiểu con trỏ Để tránh những hạn chế trên, ngôn ngữ C++ cung cấp dữ liệu kiểu con trỏ (pointer) với các đặc điểm: ─Chỉ phát sinh trong quá trình thực hiện chương trình ─Khi chạy chương trình, kích thước của biến, vùng nhớ và địa chỉ vùng nhớ được cấp phát cho biến có thể thay đổi.
- Giới thiệu dữ liệu kiểu con trỏ ─Sau khi sử dụng xong có thể giải phóng để tiết kiệm chỗ trong bộ nhớ. ─Kích thước của biến con trỏ không phụ thuộc vào kiểu dữ liệu, luôn có kích thước cố định là 2 byte (tùy thuộc vào hệ điều hành).
- Khai báo và sử dụng biến con trỏ
- Khai báo biến con trỏ • Cú pháp: * • Ý nghĩa: Khai báo một biến có tên là dùng để chứa địa chỉ của các biến có kiểu là .
- Khai báo biến con trỏ • Ví dụ : int *pa ; // Khai báo biến con trỏ pa kiểu int. float *pf; // Khai báo biến con trỏ pf kiểu float • Mỗi biến đều trỏ tới một kiểu dữ liệu khác nhau nhưng cả hai đều là con trỏ và chiếm một lượng bộ nhớ như nhau • Dữ liệu mà chúng trỏ tới chiếm lượng bộ nhớ khác nhau, một kiểu int, một kiểu float. • Giá trị của một biến con trỏ là địa chỉ mà nó trỏ tới.
- Khai báo biến con trỏ • Nếu chưa muốn khai báo kiểu dữ liệu mà con trỏ pa đang chỉ đến, ta có thể khai báo: void *pa; • Khi cần, ta cho con trỏ pa chỉ đến kiểu dữ liệu muốn khai báo. • Tác dụng của khai báo này là dành ra 2 bytes bộ nhớ để cấp phát cho biến con trỏ pa.
- Các thao tác trên con trỏ
- Gán địa chỉ của biến cho biến con trỏ (&) • Toán tử & dùng để định vị con trỏ đến địa chỉ của một biến đang làm việc. • Cú pháp: =& Ý nghĩa: Gán địa chỉ của biến cho con trỏ .
- Ví dụ: • int a=25, x; • Int *y; • x= a; //Gán giá trị của biến a cho biến x, • y=&a; //gán địa chỉ của biến a cho con biến y
- Toán tử tham chiếu (*) • Toán tử (*) để truy cập đến nội dung của ô nhớ mà con trỏ chỉ tới. • Cú pháp: * • Với cách truy cập này thì * có thể coi là một biến có kiểu được mô tả trong phần khai báo biến con trỏ.
- Ví dụ: a=*p ; // biến a mang giá trị của ô nhớ do p trỏ tới: 25 a = p; // biến a mang giá trị trong ô nhớ p :1776
- Ví dụ: #include #include int main () { int a = 5, b = 15; int *p; //biến con trỏ p kiểu int p = &a; // con trỏ p chứa địa chỉ của a *p = 10; //gán giá trị 10 cho ô nhớ do p trỏ tới p = &b; // con trỏ p chứa địa chỉ của b *p = 20; //gán giá trị 20 cho ô nhớ do p trỏ tới cout << "a =" << a << endl; cout<<”b =" << b; getch(); return 0; } // kết quả a=10; b=20
- Ví dụ #incude #include void main () { int a = 5, b = 15; int *p1, *p2; p1 = &a; // p1 chỉ đến địa chỉ của value1 p2 = &b; // p2 chì đến địa chỉ của value2 *p1 = 10; // giá trị trỏ bởi p1 = 10=>a=10 *p2 = *p1; // giá trị trỏ bởi p2 = giá trị trỏ bởi p1 p1 = p2; // p1 = p2 (phép gán con trỏ) *p1 = 20; // giá trị trỏ bởi p1 = 20 cout << "a = " << a << endl; cout<<"b = " << b; getch();//ke qua a=10, b=20 }
- Một số phép toán trên con trỏ • Việc thực hiện các phép tính số học với con trỏ có kết quả phụ của các phép tính phụ thuộc vào kích thước của kiểu dữ liệu mà biến con trỏ trỏ tới.
- Phép gán con trỏ Hai con trỏ cùng kiểu có thể gán cho nhau Ví dụ: int a, *p, *a ; float *f; a = 5 ; p = &a ; a = p ; /* đúng */ f = p ; /* sai do khác kiểu */
- Phép gán con trỏ • Ép kiểu con trỏ theo cú pháp: ( *) • Ví dụ: int a, *p, *a,*q ; float *f; a = 5 ; p = &a ; q = p ; /* đúng */ f = (float*)p; /* Đúng nhờ ép kiểu*/
- Cộng, trừ con trỏ với một số nguyên • Ta có thể cộng (+), trừ (-) 1 con trỏ với 1 số nguyên N nào đó. • Kết quả trả về là 1 con trỏ. Con trỏ này chỉ đến vùng nhớ cách vùng nhớ của con trỏ hiện tại N phần tử.
- Ví dụ: int *pa; pa = (int*) malloc(20); /* Cấp phát vùng nhớ 20 byte=10 số nguyên*/ int *pb, *pc; pb = pa + 7; pc = pb - 3;
- Cộng, trừ con trỏ với một số nguyên • Phép trừ 2 con trỏ cùng kiểu sẽ trả về 1 giá trị nguyên (int). Đây chính là khoảng cách (số phần tử) giữa 2 con trỏ đó. • Ví dụ : pc-pa=4. • Lưu ý: không thể cộng 2 con trỏ với nhau. • Các phép toán tăng/giảm (++/ ) một ngôi cũng được áp dụng trên biến con trỏ
- Ví dụ: char *mychar; short *myshort; long *mylong; mychar = mychar + 1; myshort = myshort + 1; mylong = mylong + 1;
- Lưu ý • Toán tử (++) và ( ) có độ ưu tiên lớn hơn toán tử tham chiếu (*) *p++; *p++ = *q++; • Lệnh *p++ tương đương với *(p++) : thực hiện là tăng p • Lệnh *p++ = *q++; cả hai toán tử tăng (++) đều được thực hiện sau khi giá trị của *q được gán cho *p và sau đó cả q và p đều tăng lên 1. Lệnh này tương đương với: *p = *q; p++; q++;
- Con trỏ và mảng. • Những phần tử của mảng được xác định bằng chỉ số trong mảng, nhưng chúng cũng có thể được xác định qua biến con trỏ. • Ví dụ: int numbers [20]; int * p; p = numbers;
- Vì con trỏ cũng có mọi tính chất của một biến nên tất cả các biểu thức có con trỏ trong ví dụ dưới đây là hoàn toàn hợp lệ: #include int main () { int A[5]; int * p; p = A; *p = 10; p++; *p = 20; p = &A[2]; *p = 30; p = A + 3; *p = 40; p = A; *(p+4) = 50; for (int n=0; n<5; n++) cout << A[n] << ", "; return 0; }
- Truy cập các phần tử mảng theo dạng con trỏ • Để truy cập các phần tử của mảng theo dạng con trỏ ta dùng cú pháp sau: & [0] tương đương với & [ ] tương đương với + [ ] tương đương với *( + )
- Ví dụ: Cho mảng 1 chiều a có 5 phần tử kiểu số nguyên, truy cập các phần tử theo kiểu mảng và theo kiểu con trỏ. #include #include void NhapMang(int a[ ], int N) { int i; for(i=0;i >a[i]; } }
- Nhập mảng theo dạng con trỏ void NhapContro(int a[ ], int N) { int i; for(i=0;i >*(a+i); } }
- void main() { int a[20],N,i; clrscr(); cout >N; NhapMang(a,N); /* NhapContro(a,N)*/ cout<<"Truy cap theo kieu mang: "; for(i=0;i<N;i++) cout<<setw(3)<<a[i]; cout<<"Truy cap theo kieu con tro: "; for(i=0;i<N;i++) cout<<setw(3)<<*(a+i); getch(); }
- Truy xuất từng phần tử đang được quản lý bởi con trỏ theo dạng mảng • Cú pháp: [ ] tương đương với *( + ) & [ ] tương đương với ( + )
- Ví dụ int a[]; a[3] ; //truy xuất đến phần tử thứ 4 trong mảng a. • Nếu sử dụng chỉ số thì máy tính sẽ dựa vào chỉ số này và địa chỉ bắt đầu của vùng nhớ dành cho mảng để xác định địa chỉ của phần tử mà ta muốn truy xuất tới
- Ví dụ Để truy xuất phần tử a[3] thì máy tính sẽ xác định địa chỉ của phần tử này như sau: &a[3]= a[0] + (3*2) //giả sử kiểu int chiếm 2 byte trong bộ nhớ
- Ví dụ #include #include void main() { const int SIZE=5; int i, *addr, a[SIZE]={98,87,76,65,54}; clrscr(); addr=&a[0]; for (i=0;i<SIZE;i++) cout<<a[i]<<" "; (a) cout <<endl; for (i=0;i<SIZE;i++) cout<<*(addr+i)<<" "; (b) getch(); return; }
- Ví dụ • addr=&a[0] // địa chỉ bắt đầu của mảng được gán vào con trỏ addr. • (addr+i) // lưu địa chỉ của phần tử thứ i trong mảng và • *(addr+i) // giá trị của phần tử thứ i trong mảng.
- Ví dụ Phần tử Địa chi của phần tử thứ i Giá trị của phần tử thứ i thứ i Sử dụng toán Sử dụng Sử dụng con của mảng tử con trỏ Sử dụng chỉ số trỏ a a tham chiếu & addr 0 &a[0] addr a[0] *(addr) 1 &a[1] addr+1 a[1] *(addr+1) 2 &a[2] addr+2 a[2] *(addr+2) 3 &a[3] addr+3 a[3] *(addr+3) 4 &a[4] addr+4 a[4] *(addr+4)
- Truyền địa chỉ cho tham số bằng con trỏ • Phương pháp truyền địa chỉ cho tham số sử dụng tham chiếu có nhược điểm là khi gọi hàm ta không biết là dữ liệu truyền cho hàm là tham chiếu hay tham trị. • Gọi hàm swap(i,j) • Gọi hàm Tinh(i,j) • Dựa vào nguyên mẫu: void swap(int& , int&) void Tinh(int , int)
- Truyền địa chỉ cho tham số bằng con trỏ • Ngược lại, phương pháp truyền địa chỉ cho tham số bằng con trỏ được thể hiện rỏ ràng trong cả nguyên mẫu lẫn lời gọi hàm. Ví dụ: • Gọi hàm: swap(&i,&j); • void swap(int* x, int* y)//nguyên mẫu
- void swap(int*, int*); void main() { int i=5,j=10; clrscr(); cout <<"\n Truoc khi goi ham swap\n" <<"i= "<<i<<" " <<"j= "<<j <<endl; //I=5 j=10 swap(&i,&j); cout <<"Sau khi goi ham swap\n" <<"i= "<<i<<" " <<"j= "<<j <<endl; //I=10 j=5 getch(); return; } void swap(int* x, int* y) { int tam; tam=*x; *x=*y; *y=tam; return; }
- Bộ nhớ động • Thông thường trong các chương trình, các biến được khai báo và sử dụng đều có kích thước cố định và không thể thay đổi trong thời gian chương trình chạy. • Tuy nhiên, một số trường hợp ta cần một lượng bộ nhớ mà kích thứơc của nó chỉ có thể được xác định khi chương trình chạy
- Bộ nhớ động • Ví dụ khi nhận thông tin từ người dùng để xác định lượng bộ nhớ cần thiết. • Một vùng bộ nhớ khác gọi là heap được cung cấp. • Heap được sử dụng cho việc cấp phát động các khối bộ nhớ trong thời gian thực thi chương trình. • Vì thế heap được gọi là bộ nhớ động (dynamic memory).
- Bộ nhớ động • Có hai toán tử được sử dụng cho việc cấp phát và thu hồi các khối bộ nhớ trên heap: • Toán tử new • Toán tử delete
- Toán tử new • Toán tử new nhận một kiểu như là một đối số và được cấp phát một khối bộ nhớ cho một đối tượng của kiểu đó. Nó trả về một con trỏ tới khối bộ nhớ đã được cấp phát. pointer = new type //cấp phát bộ nhớ chứa 1 phần tử kiểu hoặc pointer = new type [elements]//cấp phát một khối nhớ (một mảng) gồm các phần tử kiểu type.
- Ví dụ: int *ptr = new int; // cấp phát 1 vùng nhớ lưu trữ một số nguyên char *str = new char[10]; //cấp phát vùng nhớ lưu trữ một mảng 10 ký tự
- Ví dụ: int * a; a = new int [5]; • Trong trường hợp này hệ điều hành dành chỗ cho 5 phần tử kiểu int trong bộ nhớ và trả về một con trỏ trỏ đến đầu của khối nhớ. Vì vậy lúc này a trỏ đến một khối nhớ gồm 5 phần tử int.
- Lưu ý: Sự khác nhau giữa việc khai báo một mảng với việc cấp phát bộ nhớ cho một con trỏ: • Khai báo một mảng : Kích thước của một mảng phải là một hằng, • Cấp phát bộ nhớ động: cho phép cấp phát bộ nhớ trong quá trình chạy chương trình với kích thước bất kì.
- Lưu ý: • Bộ nhớ động được quản lý bởi hệ điều hành và trong các môi trường đa nhiệm có thể chạy một lúc vài chương trình, có một khả năng có thể xảy ra là hết bộ nhớ để cấp phát. • Nếu điều này xảy ra và hệ điều hành không thể cấp phát bộ nhớ như chúng ta yêu cầu với toán tử new, một con trỏ null (zero) sẽ được trả về. • Vì vậy khi dùng bộ nhớ động ta cần phải kiểm tra xem con trỏ trả về bởi toán tử new có bằng null hay không
- Toán tử delete. • Bộ nhớ động chỉ cần thiết trong một khoảng thời gian nhất định, khi nó không cần dùng đến nữa thì nó sẽ được giải phóng để có thể cấp phát cho các nhu cầu khác. • Để thực hiện việc này ta dùng toán tử delete.
- Toán tử delete. Cú pháp delete pointer; //giải phóng bộ nhớ được cấp phát cho một phần tử bởi toán tử new. hoặc delete [ ] pointer;//giải phóng một khối nhớ gồm nhiều phần tử (mảng). được cấp phát bởi toán tử new
- #include #include #include int main () { char input [100]; int i,n; int * a; cout << "Nhap so phan tu: "; cin.getline (input,100); i=atol (input); a= new int[i]; if (a == NULL) exit (1); for (n=0; n<i; n++) { cout << "Enter number: "; cin.getline (input,100); a[n]=atol (input); } cout << "You have entered: "; for (n=0; n<i; n++) cout << a[n] << ", "; delete[] a; getch(); return 0; }
- void main(void) { Ví dụ:Viết chương trình tạo giá trị cho một mảng động và in mảng lên longmàn int hình. a,i; Số phần tử của mảng được nhập vào khi thực hiện chương int*trình. arr; cout >a; if (a>0) { arr=new int[a]; if (arr!=NULL) { randomize; for (i=0;i<num_arr;i++) arr[i]=random(100); for (i=0;i<num_arr;i++) cout <<*(arr+i)<<" "; delete(arr); } else cout <<"khong du bo nho"; getch(); return; }
- NULL • Null là một hằng số được định nghĩa trong thư viện C++ dùng để biểu thị con trỏ null. • Nếu hằng số này chưa định nghĩa thì có thể tự định nghĩa nó: #define NULL 0 • Dùng 0 hay NULL khi kiểm tra con trỏ là như nhau nhưng việc dùng NULL với con trỏ được sử dụng rất rộng rãi và điều này được khuyến khích để giúp cho chương trình dễ đọc hơn.
- Bộ nhớ động trong ANSI-C • Toán tử new và delete chỉ có trong C++ • Trong ngôn ngữ C, để sử dụng bộ nhớ động phải sử dụng thư viện stdlib.h. Cách này cũng hợp lệ trong C++ và nó vẫn còn được sử dụng trong một số chương trình.
- Bộ nhớ động trong ANSI-C • Hàm malloc: Là một hàm tổng quát để cấp phát bộ nhớ động cho con trỏ void * malloc (size_t nbytes); • Trong đó • nbytes là số byte muốn gán cho con trỏ. • Hàm này trả về một con trỏ kiểu void*, vì vậy phải chuyển đổi kiểu sang kiểu của con trỏ đích
- Ví dụ: char * c; c = (char *) malloc (10); //cấp phát cho con trỏ c một khối nhớ 10 byte.
- Lưu ý • Khi muốn cấp phát một khối dữ liệu có kiểu khác char (>1 byte) thì phải nhân số phần tử mong muốn với kích thước của chúng. • Để lấy kích thước ta sử dụng toán tử sizeof, toán tử này trả về kích thước của một kiểu dữ liệu cụ thể.
- Ví dụ: int * a; a = (int *) malloc (5 * sizeof(int)); //cấp phát cho a một khối nhớ gồm 5 số nguyên kiểu int, kích cỡ của kiểu dữ liệu này có thể bằng 2, 4 hay hơn tùy thuộc vào hệ thống mà chương trình được dịch.
- Bộ nhớ động trong ANSI-C • Hàm calloc: calloc hoạt động rất giống với malloc, chỉ khác nhau ở cách khai báo void * calloc (size_t nelements, size_t size); • Trong khai báo sử dụng hai tham số. Hai tham số này được nhân với nhau để có được kích thước tổng cộng của khối nhớ cần cấp phát. • Tham số thứ nhất (nelements) là số phần tử và tham số thứ hai (size) là kích thước của mỗi phần tử.
- Ví dụ: int * a; a = (int *) calloc (5, sizeof(int)); Một điểm khác nhau nữa giữa malloc và calloc là calloc khởi tạo tất cả các phần tử của nó về 0.
- Bộ nhớ động trong ANSI-C • Hàm realloc: thay đổi kích thước của khối nhớ đã được cấp phát cho một con trỏ. • Cú pháp: void * realloc (void * pointer, size_t size); • Tham số pointer nhận vào một con trỏ đã được cấp phát bộ nhớ hay một con trỏ null. • Tham số size chỉ định kích thước của khối nhớ mới. Hàm này sẽ cấp phát size byte bộ nhớ cho con trỏ.
- Bộ nhớ động trong ANSI-C • Hàm free: Hàm này giải phóng một khối nhớ động đã được cấp phát bởi malloc, calloc hoặc realloc. • Cú pháp: void free (void * pointer);