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

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ả 62fe1c và 62fdf0 chỉ là kết quả minh họa.

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ác
Nế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
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

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

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

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ụ sau

int 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 + iTrỏ đến số nguyên được lưu trữ i vị trí sau a
p – iTrỏ đế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

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

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

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

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

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

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

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

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ệu

Giả 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_bTrả 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_bTrả 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_bTrả 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_bTrả 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.

Tôi sẽ đưa ra một ví dụ mẫu về việc so sánh con trỏ ptr_a int main(){ int a; int b; int *ptr_a; int *ptr_b; ptr_a = &a; ptr_b = &b; printf("ptr_a la: %x \n",ptr_a); printf("ptr_b la: %x ",ptr_b); //Ket qua so sanh bang 1 nghia la ptr_a be hon ptr_b nguoc lai la ptr_a khong be hon ptr_b printf("\n
Ket qua phep so sanh ptr_a
ptr_a la: 62fe0c

ptr_b la: 62fe08

Ket qua phep so sanh ptr_a

Kết quả bằng 0 vậy nên ptr_a không bé hơn ptr_b

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ỏ NULL

Con 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
Chú ý: Con trỏ Null cũng có một địa chỉ trong bộ nhớ, tuy nhiên giá trị ở địa chỉ đó bằng rỗng (hay còn hiểu là NULL)

Để 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 == NULLTrả 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

Các khóa học qua video:Lập trình C Java C# SQL Server PHP HTML5-CSS3-Java
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ả: Ezoicreport this ad
Cá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