1.1 Con trỏ là gì?
Đầu tiên, ta có thể hiểu rằng khi khai báo biến, các biến đó đều có một vùng chứa nhất định trong bộ nhớ hay còn gọi là địa chỉ trong bộ nhớ, xét ví dụ dưới đây tôi khai báo 2 biến a và biến b sau đó kiểm tra xem chúng ở địa chỉ nào trong bộ nhớ bằng cách printf 2 biến a, b ra màn hình sau đó sử dụng ký hiệu &a, &b và sử dụng kiểu %x (thập lục phân) trong việc tìm ra địa chỉ của 2 biến trong bộ nhớ
#include int main(){ int a = 10; int b<10>; printf("Dia chi cua a trong bo nho la: %x",&a); printf("\n
Dia chi cua b trong bo nho la: %x",&b); return 0;}
Dia chi cua a trong bo nho la: 62fe1c Dia chi cua b trong bo nho la: 62fdf0 |
Bạn đang xem: Bài tập con trỏ trong c
Như vậy con trỏ có liên quan gì đến bộ nhớ và các biến? Cùng xem các định nghĩa dưới đây về con trỏ:
Con trỏ là một biến, nó chứa địa chỉ ô nhớ của một biến khácNếu một biến chứa địa chỉ của một biến khác, thì biến này được gọi là con trỏ trỏ đến biến thứ hai
Con trỏ cung cấp phương thức truy xuất gián tiếp đến giá trị của một phần tử dữ liệu
Các con trỏ có thể trỏ đến các biến có kiểu dữ liệu cơ bản như int, char, double, hay dữ liệu tập hợp như mảng hoặc cấu trúc.
1.2 Con trỏ được sử dụng làm gì?
Các tình huống con trỏ có thể được sử dụng:Để trả về nhiều hơn một giá trị từ một hàmĐể truyền mảng và chuỗi từ một hàm đến một hàm khác thuận tiện hơn
Để làm việc với các phần tử của mảng thay vì truy xuất trực tiếp vào các phần tử này
Để cấp phát bộ nhớ và truy xuất bộ nhớ (Cấp phát bộ nhớ trực tiếp)2.Sử dụng con trỏ trong C
2.1 Khai báo biến con trỏ
Cú pháp khai báo biến con trỏ như sau:
type *name
Trong đó:
Type là kiểu dữ liệu (int, float, double, char…..)Name là tên của con trỏDấu * (dấu sao) trước tên là thành phần bắt buộc để khai báo rằng đó là biến con trỏ
Ví dụ dưới đây tôi khai báo con trỏ với một số kiểu dữ liệu thường gặp
#include int main(){ int *a; float *b; double *c; char *d;}
2.2 Các toán tử con trỏ
Trong con trỏ có 2 toán tử đặc biệt được sử dụng là toán từ (&) và (*)(&) là toán tử trả về ô nhớ của biến(*) là toán tử trả về giá trị chứa trong vùng nhớ được trỏ đến bởi biến con trỏVí dụ dưới đây tôi có 2 biến đó là: a và biến con trỏ *p, tôi sử dụng toán tử (&) để trả về ô nhớ của biến a bằng cách khai báo biến con trỏ *p và gán biến con trỏ p = &a
#include int main(){ //Khai bao bien a int a = 10; // Khai bao bien con tro *p int *p; // Gan con tro *p = &a p = &a; printf("Dia chi o nho cua bien la: %x",p);}
Dia chi o nho cua bien la: 62fe14 |
Giả sử tôi muốn lấy giá trị của con trỏ *p ở trên thì sao??
Như ở trên tôi đã đề cập đến toán tử (*) để lấy ra giá trị của con trỏ:
#include int main(){ //Khai bao bien a int a = 10; // Khai bao bien con tro *p int *p; // Gan con tro *p = &a p = &a; printf("Dia chi o nho cua bien la: %x",p); // Hien thi gia tri con tro bang cach su dung toan tu *p printf("\n
Gia tri cua con tro p la: %d",*p);}
Dia chi o nho cua bien la: 62fe14 Gia tri cua con tro p la: 10 |
Ngoài ra tôi cũng có thể gán lại giá trị cho biến a bằng cách gán thông qua con trỏ *p
#include int main(){ //Khai bao bien a ban dau int a = 10; printf("Gia tri bien a ban dau la: %d",a); // Khai bao bien con tro *p int *p; // Gan con tro *p = &a p = &a; //Gan gia tri moi cho bien a thong qua con tro *p *p = 20; printf("\n
Gia tri cua bien a sau khi duoc gan lai: %d",a);}
Gia tri bien a ban dau la: 10 Gia tri cua bien a sau khi duoc gan lai: 20 |
2.3 Phép toán con trỏ
Con trỏ chỉ có thể thực hiện đươc hai phép toán đó là phép cộng và trừ trên con trỏ. Tôi có một ví dụ sauint a, *p; p = &a; a = 500; p++; Giả sử biến a được lưu trữ tại địa chỉ 1000 , sau khi gán p = &a thì p lưu giá trị 1000. Sau biểu thức “p++;” p sẽ có giá trị là 1004 do số nguyên có kích thước là 4 bytes nên khi tăng p lên một đơn vị sẽ là 1000 + 4 = 1004
Tất cả con trỏ sẽ tăng hoặc giảm trị theo kích thước của kiểu dữ liệu mà chúng đang trỏ đến ví dụ kiểu int ở trên kích thước là 4bytes nên tăng kích thước là +4 và giảm kích thước là -4. Các bạn có thể đọc lại bài các kiểu dữ liệu căn bản trong C để hiểu rõ thêm một số kích thước của một số kiểu dữ liệu khác
Xét a và p trên ví dụ trên tôi có một số phép toán con trỏ như sau:
Phép toán | Ý nghĩa |
++p hoặc p++ | Trỏ đến số nguyên được lưu trữ kế tiếp sau a |
–p hoặc p — | Trỏ đến số nguyên được lưu trữ liền trước a |
p + i | Trỏ đến số nguyên được lưu trữ i vị trí sau a |
p – i | Trỏ đến số nguyên được lưu trữ i vị trí trước a |
(*p)++ | Tăng giá trị của a lên 1 |
Chú ý vào phép toán p++ và p + i, vì đây là 2 phép toán sẽ được sử dụng nhiều khi thao tác với con trỏ. (Đặc biệt là thao tác con trỏ với mảng ở bài sau)
Xét ví dụ dưới đây cho các phép toán:
Phép toán: ++p hoặc p++
#include int main(){ int a; int *p; p = &a; a = 500; printf("Dia chi o nho ban dau la: %x", p); p++; printf("\n
Dia chi o nho khi p++ la: %x", p);}
Dia chi o nho ban dau la: 62fe14 Dia chi o nho khi p++ la: 62fe18 |
Phép toán: –p hoặc p –
#include int main(){ int a; int *p; p = &a; a = 500; printf("Dia chi o nho ban dau la: %x", p); p--; printf("\n
Dia chi o nho khi p++ la: %x", p);}
Dia chi o nho ban dau la: 62fe14 Dia chi o nho khi p++ la: 62fe10 |
Phép toán: p + i (ở đây tôi dùng vòng lặp để p cộng với i = 2 lần, nghĩa là p + 2)
#include int main(){ int a; int *p; p = &a; a = 500; printf("Dia chi o nho ban dau la: %x", p); //Dung vong lap de cong p voi i lan for (int i = 0; i
Dia chi o nho ban dau la: 62fe10 Dia chi o nho khi p + 0 la: 62fe10 Dia chi o nho khi p + 1 la: 62fe14 Dia chi o nho khi p + 2 la: 62fe18 |
Phép toán: p – i (ở đây tôi dùng vòng lặp để p trừ với i = 2 lần, nghĩa là p – 2)
#include int main(){ int a; int *p; p = &a; a = 500; printf("Dia chi o nho ban dau la: %x", p); //Dung vong lap de tru p voi i lan for (int i = 0; i
Dia chi o nho ban dau la: 62fe10 Dia chi o nho khi p – 0 la: 62fe0c Dia chi o nho khi p – 1 la: 62fe08 Dia chi o nho khi p – 2 la: 62fe04 |
Phép toán: (*p)++ (tăng giá trị của a lên 1 đơn vị)
#include int main(){ int a; int *p; p = &a; a= 500; printf("Gia tri a ban dau la: %d", a); //Tang gia tri cua a len 1 (*p)++; printf("\n
Gia tri a sau khi (*p)++ la: %d", a);}
Gia tri a ban dau la: 500 Gia tri a sau khi (*p)++ la: 501 |
2.3 So sánh hai con trỏ
Hai con trỏ có thể được so sánh trong một biểu thức quan hệ nếu chúng trỏ đến các biến có cùng kiểu dữ liệuGiả sử ptr_a và ptr_b là hai biến con trỏ trỏ đến các phần tử dữ liệu a và b. Trong trường hợp này, các phép so sánh sau là có thể:
ptr_a ptr_b | Trả về giá trị true nếu a được lưu trữ ở vị trí sau b |
ptr_a ptr_a và ptr_b trỏ đến cùng một vị trí | |
ptr_a >= ptr_b | Trả về giá trị true nếu a được lưu trữ ở vị trí sau b hoặc ptr_a và ptr_b trỏ đến cùng một vị trí |
ptr_a == ptr_b | Trả về giá trị true nếu cả hai con trỏ ptr_a và ptr_b trỏ đến cùng một phần tử dữ liệu. |
ptr_a != ptr_b | Trả về giá trị true nếu cả hai con trỏ ptr_a và ptr_b trỏ đến các phần tử dữ liệu khác nhau nhưng có cùng kiểu dữ liệu. |
Ket qua phep so sanh ptr_a
ptr_a la: 62fe0c ptr_b la: 62fe08 Ket qua phep so sanh ptr_a |
Các bạn có thể tự mình kiểm tra các phép so sánh khác ở trên.
Chú ý: Địa chỉ ô nhớ trên mỗi máy tính khi thực thi chương trình trên là khác nhau. Kết quả trên chỉ là minh họa
3. Con trỏ NULLCon trỏ NULL nghĩa là việc ta gán giá trị cho con trỏ đó bằng NULL (rỗng).
Lưu ý rằng NULL ở đây không giống với số 0, nếu gán con trỏ bằng số 0 thì con trỏ đó không phải là con trỏ NULL mà ngược lại con trỏ được gán bằng số 0 cũng giống như mọi con trỏ bình thường được gán bởi các giá trị khác như 1,2,3,.v.v
#include int main (){ int *p = NULL; printf("Dia chi cua con tro p la: %x\n", &p); return 0;}
Dia chi cua con tro p la: 62fe18 |
Để kiểm tra con trỏ có phải là con trỏ NULL hay không ta sử dụng câu lệnh dưới đây:
p == NULL | Trả về giá trị true (hay số 1) nếu p được gán giá trị NULL |
#include int main (){ int *p = NULL; printf("Ket qua kiem tra con tro NULL la: %d", p == NULL); return 0;}
Ket qua kiem tra con tro NULL la: 1 |
Script
Nếu bạn chưa có kiến thức về con trỏ, xin mời xem bài viết Con trỏ (Pointer).Con trỏ giúp dễ hàng hơn trong việc làm việc thao tác với địa chỉ các biến.Ta có thể khẳng định rằng địa chỉ được gán cho một con trỏ phải cùng loại với quy định trong khai báo con trỏ. Ví dụ, nếu ta khai báo con trỏ int, thì con trỏ int này không thể trỏ đến biến float hoặc một số loại biến khác, nghĩa là nó chỉ có thể trỏ đến biến kiểu int. Để khắc phục vấn đề này, ta sử dụng con trỏ void. Một con trỏ đến void có nghĩa là một con trỏ chung có thể trỏ đến bất kỳ loại dữ liệu nào. Chúng ta có thể gán địa chỉ của bất kỳ loại dữ liệu nào cho con trỏ void và con trỏ void có thể được gán cho bất kỳ loại con trỏ nào mà không thực hiện bất kỳ kiểu chữ rõ ràng nào.
Cú pháp của con trỏ void
void *tên_con_trỏ;
Ví dụ:
void *p;
Ta xét một số ví dụ sau:int i=9; // integer variable initialization.int *p; // integer pointer declaration.float *fp; // floating pointer declaration.
void *ptr; // void pointer declaration.p=fp; // incorrect.fp=&i; // incorrectptr=p; // correctptr=fp; // correctptr=&i; // correct
Kích thước của con trỏ void trong C
Kích thước của con trỏ void trong C giống với kích thước của con trỏ kiểu ký tự. Theo nhận thức của C, việc thể hiện một con trỏ thành void giống như con trỏ của kiểu ký tự. Kích thước của con trỏ sẽ thay đổi tùy thuộc vào nền tảng mà bạn đang sử dụng.Hãy xem ví dụ dưới đây:#include main() { void *ptr = NULL; //void pointer int *p = NULL;// integer pointer char *cp = NULL;//character pointer float *fp = NULL;//float pointer //size of void pointer printf("size of void pointer = %d\n\n",sizeof(ptr)); //size of integer pointer printf("size of integer pointer = %d\n\n",sizeof(p)); //size of character pointer printf("size of character pointer = %d\n\n",sizeof(cp)); //size of float pointer printf("size of float pointer = %d\n\n",sizeof(fp)); return 0; } Kết quả:Ưu điểm của con trỏ void
Sau đây là những lợi thế của một con trỏ void:Hàm malloc () và calloc () trả về con trỏ void, vì vậy các hàm này có thể được sử dụng để phân bổ bộ nhớ của bất kỳ loại dữ liệu nào.#include #include int main() { int a=90; int *x = (int*)malloc(sizeof(int)) ; x=&a; printf("Value which is pointed by x pointer : %d",*x); return 0; } Kết quả: Con trỏ void trong C cũng có thể được sử dụng để thực hiện các hàm chung trong C.Xem thêm: Hãy Yêu Bản Thân Mình Trước, Hãy Yêu Thương Bản Thân Mình
Một số điểm quan trọng liên quan đến con trỏ void là:Hủy bỏ một con trỏ trống trong CCon trỏ void trong C không thể được hủy đăng ký trực tiếp. Hãy xem ví dụ dưới đây:#include int main() { int a=90; void *ptr; ptr=&a; printf("Value which is pointed by ptr pointer : %d",*ptr); return 0; } Trong đoạn mã trên, * ptr là một con trỏ void đang trỏ đến biến số nguyên "a". Như chúng ta đã biết rằng con trỏ void không thể bị hủy đăng ký, do đó đoạn mã trên sẽ đưa ra lỗi thời gian biên dịch vì chúng ta đang in trực tiếp giá trị của biến được trỏ bởi con trỏ "ptr".Kết quả:Bây giờ, chúng tôi viết lại mã trên để loại bỏ lỗi:#include int main() { int a=90; void *ptr; ptr=&a; printf("Value which is pointed by ptr pointer : %d",*(int*)ptr); return 0; } Trong đoạn mã trên, chúng tôi đánh máy con trỏ void sang con trỏ nguyên bằng cách sử dụng câu lệnh được đưa ra dưới đây:(int *) ptr;Sau đó, chúng tôi in giá trị của biến được chỉ ra bởi con trỏ void "ptr" bằng cách sử dụng câu lệnh được đưa ra dưới đây:* (int *) ptr;Kết quả: Phép toán số học trên con trỏ voidChúng ta không thể áp dụng các phép toán số học trên các con trỏ void trong C trực tiếp. Chúng ta cần áp dụng kiểu chữ phù hợp để có thể thực hiện các phép toán số học trên các con trỏ void.
Hãy xem ví dụ dưới đây:#include int main() { float a<4>={6.1,2.3,7.8,9.0}; void *ptr; ptr=a; for(int i=0;i { printf("%f,",*ptr); ptr=ptr+1; // Incorrect. }}Đoạn mã trên cho thấy lỗi thời gian biên dịch " sử dụng biểu thức void " không hợp lệ vì chúng ta không thể áp dụng trực tiếp các phép toán số học trên con trỏ void, tức là ptr = ptr + 1.Hãy viết lại mã trên để loại bỏ lỗi:
#include int main() { float a<4>={6.1,2.3,7.8,9.0}; void *ptr; ptr=a; for(int i=0;i { printf("%f,",*((float*)ptr+i)); }}Đoạn mã trên chạy thành công khi chúng ta áp dụng việc truyền đúng cho con trỏ void, nghĩa là (float *) ptr và sau đó chúng ta áp dụng phép toán số học, tức là * ((float *) ptr + i).Kết quả:Tại sao chúng ta sử dụng con trỏ void?
Chúng tôi sử dụng con trỏ void vì khả năng tái sử dụng của nó. Con trỏ trống có thể lưu trữ đối tượng thuộc bất kỳ loại nào và chúng ta có thể truy xuất đối tượng thuộc bất kỳ loại nào bằng cách sử dụng toán tử gián tiếp với kiểu chữ phù hợp.Hãy hiểu qua một ví dụ như sau:#include int main() { int a=56; // initialization of a integer variable "a". float b=4.5; // initialization of a float variable "b". char c="k"; // initialization of a char variable "c". void *ptr; // declaration of void pointer. // assigning the address of variable "a". ptr=&a; printf("value of "a" is : %d",*((int*)ptr)); // assigning the address of variable "b". ptr=&b; printf("\nvalue of "b" is : %f",*((float*)ptr)); // assigning the address of variable "c". ptr=&c; printf("\nvalue of "c" is : %c",*((char*)ptr)); return 0; } Kết quả: report this adCác khóa học qua video:Lập trình C Java C# SQL Server PHP HTML5-CSS3-Java
Script« Prev: Lập trình C: Các hàm xử lý tập tin