Con trỏ trong C: Con trỏ trong lập trình C là gì? Các loại
⚡ Tóm tắt thông minh
Trong ngôn ngữ C, con trỏ là các biến lưu trữ địa chỉ bộ nhớ của một biến khác. Chúng cho phép cấp phát bộ nhớ động, xử lý mảng và chuỗi hiệu quả, cũng như các cấu trúc dữ liệu phức tạp, sử dụng các toán tử truy cập địa chỉ và giải tham chiếu để đọc hoặc sửa đổi giá trị.

Con trỏ trong ngôn ngữ C là gì?
A con trỏ Trong ngôn ngữ C, con trỏ là một biến lưu trữ địa chỉ của một biến khác. Con trỏ cũng có thể tham chiếu đến một con trỏ khác hoặc đến một hàm. Con trỏ có thể được tăng hoặc giảm để trỏ đến vị trí bộ nhớ tiếp theo hoặc trước đó. Mục đích của con trỏ là tiết kiệm không gian bộ nhớ và đạt được thời gian thực thi nhanh hơn.
Cách sử dụng con trỏ trong C
Nếu ta khai báo một biến v thuộc kiểu int, v sẽ lưu trữ một giá trị. Mỗi biến, ngoài giá trị của nó, còn có một địa chỉ, đó là vị trí của nó trong bộ nhớ. Địa chỉ có thể được truy xuất bằng cách đặt dấu và (&) trước tên biến. Nếu bạn in địa chỉ của một biến, nó sẽ trông giống như một số ngẫu nhiên và có thể khác nhau trong mỗi lần chạy.
Thay vì lưu trữ một giá trị, con trỏ lưu trữ địa chỉ của một biến. Ví dụ:
int *y = &v;
| BIẾN ĐỔI | ĐIỂM |
|---|---|
| A giá trị được lưu trữ trong một tên Địa chỉ lưu trữ/bộ nhớ. | A biến việc này điểm đến địa chỉ lưu trữ/bộ nhớ của khác biến. |
Khai báo một con trỏ
Giống như các biến, con trỏ trong lập trình C Con trỏ phải được khai báo trước khi sử dụng. Khai báo con trỏ có dạng như sau:
data_type *pointer_variable_name;
Ở đây, loại dữ liệu là kiểu cơ sở của con trỏ và cho biết kiểu của biến mà con trỏ trỏ tới. Dấu sao (*), cùng ký hiệu được sử dụng cho phép nhân, là toán tử gián tiếp khai báo một con trỏ. Một số khai báo con trỏ hợp lệ là:
int *ptr_thing; /* pointer to an integer */ int *ptr1, thing; /* ptr1 points to an int; thing is an int */ double *ptr2; /* pointer to a double */ float *ptr3; /* pointer to a float */ char *ch1; /* pointer to a character */
Khởi tạo một con trỏ
Sau khi khai báo con trỏ, ta gán cho nó địa chỉ của một biến. Nếu con trỏ chưa được khởi tạo mà vẫn được sử dụng, kết quả có thể không thể đoán trước và tiềm ẩn rủi ro. Để lấy địa chỉ của một biến, hãy sử dụng toán tử dấu và (&) trước tên biến:
pointer = &variable;
Dưới đây là một chương trình đơn giản minh họa về con trỏ:
#include <stdio.h> int main() { int a = 10; // variable declaration int *p; // pointer variable declaration p = &a; // store address of a in pointer p printf("Address stored in p is: %x\n", p); // access the address printf("Value stored in p is: %d\n", *p); // access the value return 0; }
Đầu ra:
Address stored in p is: 60ff08 Value stored in p is: 10
| Operator | Ý nghĩa |
|---|---|
| * | Hàm này có hai chức năng: khai báo một con trỏ và trả về giá trị của biến được tham chiếu (giải tham chiếu). |
| & | Hàm này chỉ có một chức năng: trả về địa chỉ của một biến. |
Các loại con trỏ trong C
Con trỏ rỗng
Chúng ta tạo một con trỏ null bằng cách gán giá trị NULL trong quá trình khai báo. Điều này hữu ích khi chưa có địa chỉ nào được gán cho con trỏ. Một con trỏ null luôn chứa giá trị 0:
#include <stdio.h> int main() { int *p = NULL; // null pointer printf("The value inside variable p is: %x", p); return 0; }
Con trỏ trống
Con trỏ void, hay còn gọi là con trỏ chung, không có kiểu dữ liệu chuẩn nào. Nó được tạo bằng từ khóa void và có thể lưu trữ địa chỉ của bất kỳ biến nào:
#include <stdio.h> int main() { void *p = NULL; // void pointer printf("The size of pointer is: %d\n", sizeof(p)); return 0; }
Chó săn hoang dã
Một con trỏ được gọi là con trỏ hoang nếu nó không được khởi tạo bằng bất kỳ giá trị nào. Những con trỏ này không hiệu quả vì chúng có thể trỏ đến một vị trí bộ nhớ không xác định, điều này có thể gây ra sự cố và làm sập chương trình. Luôn luôn cẩn thận với các con trỏ hoang:
#include <stdio.h> int main() { int *p; // wild pointer printf("%d", *p); return 0; }
Các loại con trỏ khác trong C bao gồm con trỏ lơ lửng, con trỏ phức, con trỏ gần, con trỏ xa và con trỏ khổng lồ.
Con trỏ truy cập trực tiếp và gián tiếp
Trong ngôn ngữ C, có hai cách tương đương để truy cập và thao tác nội dung của một biến. Truy cập trực tiếp sử dụng chính tên biến, trong khi truy cập gián tiếp sử dụng con trỏ đến biến đó:
#include <stdio.h> int var = 1; /* declare and initialize an int variable */ int *ptr; /* declare a pointer to int */ int main(void) { ptr = &var; /* point ptr to var */ printf("\nDirect access, var = %d", var); printf("\nIndirect access, var = %d", *ptr); *ptr = 48; /* change var through the pointer */ printf("\nIndirect access, var = %d", *ptr); return 0; }
Số học con trỏ trong C
Khi làm việc với con trỏ, hãy tuân thủ các quy tắc ưu tiên sau. Các toán tử * và & có cùng độ ưu tiên với các toán tử một ngôi (phủ định !, tăng ++, giảm –). Trong cùng một biểu thức, các toán tử một ngôi *, &, !, ++, và — được đánh giá từ phải sang trái. Nếu một con trỏ P trỏ đến một biến X, thì *P có thể được sử dụng ở bất cứ nơi nào X có thể được viết. Các biểu thức sau đây tương đương nhau:
| Biểu hiện | Biểu thức tương đương |
|---|---|
| Y = *P + 1 | Y = X + 1 |
| *P = *P + 10 | X = X + 10 |
| *P ± 2 | X ± 2 |
| ++*P | ++X |
| (*P)++ | X ++ |
Trong trường hợp cuối cùng, dấu ngoặc đơn là cần thiết: vì các toán tử một ngôi * và ++ được đánh giá từ phải sang trái, nếu không có dấu ngoặc đơn thì con trỏ P sẽ được tăng lên, chứ không phải đối tượng mà nó trỏ tới. Các phép toán số học có sẵn trên con trỏ là phép gán, phép tăng và giảm, và phép cộng hoặc phép trừ.tracSử dụng một giá trị bù số nguyên, di chuyển con trỏ đi bấy nhiêu phần tử (mỗi bước bằng kích thước tính bằng byte của kiểu dữ liệu cơ sở).
Con trỏ và mảng trong C kèm ví dụ.
Theo truyền thống, chúng ta truy cập các phần tử mảng bằng chỉ số, nhưng con trỏ cũng giúp việc này dễ dàng hơn. Một con trỏ được gán tên mảng sẽ trỏ đến phần tử đầu tiên:
#include <stdio.h> int main() { int a[5] = {1, 2, 3, 4, 5}; // array initialization int *p; // pointer declaration p = a; // p points to the first element; same as &a[0] for (int i = 0; i < 5; i++) { printf("%d\n", *p); // print element p++; // move to the next element } return 0; }
Việc cộng thêm một số vào con trỏ sẽ di chuyển con trỏ về phía trước bấy nhiêu phần tử. Nếu p trỏ đến vị trí bộ nhớ 0, thì p + 1 sẽ làm cho con trỏ trỏ đến vị trí bộ nhớ 1.
Con trỏ và chuỗi C có ví dụ
Chuỗi là một mảng các đối tượng char kết thúc bằng ký tự null '\0'. Chúng ta có thể thao tác với chuỗi bằng cách sử dụng con trỏ:
#include <stdio.h> #include <string.h> int main() { char str[] = "Hello Guru99!"; char *p; p = str; printf("First character is: %c\n", *p); p = p + 1; printf("Next character is: %c\n", *p); p = str; // reset the pointer for (int i = 0; i < strlen(str); i++) { printf("%c\n", *p); p++; } return 0; }
Một cách khác để xử lý chuỗi là sử dụng mảng con trỏ:
#include <stdio.h> int main() { char *materials[] = {"iron", "copper", "gold"}; for (int i = 0; i < 3; i++) printf("%s\n", materials[i]); return 0; }
Ưu điểm của con trỏ trong C
- Con trỏ rất hữu ích để truy cập trực tiếp vào các vị trí bộ nhớ.
- Chúng cung cấp một cách hiệu quả để truy cập các phần tử của mảng.
- Chúng được sử dụng để cấp phát và giải phóng bộ nhớ động.
- Chúng được sử dụng để tạo thành các cấu trúc dữ liệu phức tạp như danh sách liên kết, đồ thị và cây.
Nhược điểm của con trỏ trong C
- Con trỏ có vẻ hơi phức tạp để hiểu.
- Chúng có thể gây ra các lỗi như lỗi phân đoạn bộ nhớ hoặc truy cập vào vị trí bộ nhớ không mong muốn.
- Việc gán giá trị không chính xác cho con trỏ có thể gây ra lỗi hỏng bộ nhớ.
- Con trỏ cũng là nguyên nhân gây ra rò rỉ bộ nhớ nếu bộ nhớ không được giải phóng.
- Lập trình viên có trách nhiệm xử lý con trỏ một cách cẩn thận.
