Con trỏ trong ngôn ngữ lập trình C là một tính năng rất hữu ích và mạnh mẽ. Tuy nhiên, việc sử dụng con trỏ một cách không đúng cách có thể gây ra các lỗi và sự cố liên quan đến bộ nhớ. Vì vậy, để sử dụng con trỏ C một cách chính xác, bạn cần hiểu rõ về nó.
Con trỏ trong C là gì?
Trong lập trình C, con trỏ là một biến dùng để lưu trữ địa chỉ bộ nhớ của một biến khác. Con trỏ cho phép chúng ta truy cập trực tiếp vào bộ nhớ, điều này rất hữu ích khi thực hiện các tác vụ như cấp phát bộ nhớ động hay làm việc với mảng.
Một con trỏ được khai báo bằng ký hiệu () đặt trước tên biến. Ví dụ: để khai báo một con trỏ tới một số nguyên, bạn sẽ sử dụng cú pháp: `int ptr;`
Các cách sử dụng con trỏ
Trong ngôn ngữ lập trình C, con trỏ được sử dụng trong các trường hợp sau:
- Tạo cấu trúc dữ liệu động (dynamic data structures): cấu trúc dữ liệu được xây dựng từ các khối bộ nhớ được phân bổ từ vùng heap, dựa theo thời gian chạy.
- Xử lý các tham số biến thiên được truyền cho hàm.
- Con trỏ trong C cung cấp một cách khác để truy cập vào thông tin lưu trữ trong mảng.
Trong một số trường hợp, lập trình viên cũng sử dụng con trỏ để tăng hiệu quả code.
Thuật toán con trỏ
Do con trỏ chứa các địa chỉ bộ nhớ, bạn có thể thực hiện các phép tính số học trên con trỏ để di chuyển chúng đến các vị trí khác nhau của bộ nhớ. Điều này thường được sử dụng trong các hoạt động của mảng, khi mà bạn sử dụng con trỏ để truy cập các phần tử của mảng.
Ví dụ, để in ra phần tử đầu tiên của một mảng số nguyên bằng con trỏ, bạn có thể sử dụng đoạn code sau:
int arr[] = {1, 2, 3};
int *p = arr; // p trỏ đến phần tử đầu tiên của arr
printf("%d\n", *p); // in ra 1
Trong đoạn code trên, p
được sử dụng để trỏ đến phần tử đầu tiên của mảng và *p
được sử dụng để lấy giá trị của phần tử đầu tiên (là 1).
Hướng dẫn sử dụng con trỏ C cơ bản trong ngôn ngữ lập trình C
Tiếp theo, chúng ta sẽ hướng dẫn bạn một vài cách sử dụng con trỏ trong C cơ bản.
Khai báo con trỏ
Để khai báo một biến con trỏ trong C, chúng ta sử dụng ký hiệu *
đứng trước tên biến. Có hai cách khai báo biến con trỏ trong ngôn ngữ C là:
int *p;
int* p;
Cả hai cách khai báo này đều mang ý nghĩa tương đương nhau. Chúng khai báo một biến con trỏ có tên "p" và chứa địa chỉ bộ nhớ của một số nguyên. Tuy nhiên, nếu bạn khai báo nhiều biến trong một câu lệnh, bạn cần thêm dấu *
trước mỗi tên biến để hệ thống ghi nhận tất cả chúng đều là con trỏ. Ví dụ: int *p, *q, *r;
ở đây, chúng ta đang khai báo ba biến con trỏ có tên "p", "q" và "r" và chúng đều chứa địa chỉ bộ nhớ của một số nguyên.
Khởi tạo con trỏ
Khi bạn khai báo một biến con trỏ, nó sẽ không tự động trỏ đến bất kỳ vị trí cụ thể nào trên bộ nhớ. Để khởi tạo một con trỏ có thể trỏ đến một biến hoặc một vị trí cụ thể, bạn cần sử dụng toán tử &
để lấy địa chỉ của biến đó.
Ví dụ, để khởi tạo con trỏ p
để trỏ tới một biến số nguyên có tên là x
, bạn sẽ viết cú pháp như sau:
int x = 42;
int* p = &x;
Hủy đăng ký một con trỏ
Khi có một con trỏ, bạn có thể truy cập và sửa đổi giá trị được lưu trữ tại vị trí đó bằng cách hủy đăng ký con trỏ.
Để hủy đăng ký một con trỏ, bạn sử dụng ký hiệu dấu *
đứng ở phía trước chính biến con trỏ. Ví dụ: để in giá trị của số nguyên mà p
trỏ tới, bạn có mẫu sau: printf("%d\n", *p);
Con trỏ tới con trỏ
Một con trỏ cũng có thể trỏ tới một biến của con trỏ khác, việc này được gọi là "con trỏ tới con trỏ". Bạn thực hiện khai báo một con trỏ tới một con trỏ bằng cách sử dụng hai dấu **
. Ví dụ:
int x = 42;
int *p = &x;
int **q = &p;
Trong ví dụ trên, q
là một con trỏ tới một con trỏ. Nó trỏ đến địa chỉ của biến p
, từ đó trỏ đến địa chỉ của biến x
.
Truyền con trỏ tới hàm
Bạn có thể truyền con trỏ trong C tới hàm dưới dạng đối số, việc này cho phép hàm sửa đổi giá trị của biến được truyền vào ban đầu. Người ta còn gọi việc chuyển con trỏ tới hàm là "truyền theo tham chiếu".
Để truyền một con trỏ tới một hàm, bạn chỉ cần khai báo tham số của hàm dưới dạng một con trỏ. Ví dụ:
void increment(int *p) {
(*p)++;
}
int main() {
int x = 42;
int *p = &x;
increment(p);
printf("%d\n", x); // in ra 43
return 0;
}
Ở ví dụ trên, hàm increment
lấy một con trỏ tới một số nguyên (int *p
) và tăng giá trị của số nguyên đó lên 1 đơn vị ((*p)++). Trong main()
, chúng ta khai báo số nguyên x
và con trỏ p
trỏ tới x
. Sau đó chúng ta gọi hàm increment
, truyền vào con trỏ p
. Sau khi gọi hàm, x
đã được tăng lên 43.
Sử dụng con trỏ trong C để phân bổ bộ nhớ động
Một trong những ứng dụng hữu ích nhất của con trỏ C là cấp phát bộ nhớ động. Điều này cho phép bạn phân bổ bộ nhớ ngay trong thời gian chạy chương trình, thay vì phải chờ đến lúc biên dịch.
Ví dụ dưới đây là cách sử dụng hàm malloc
để cấp phát động bộ nhớ và trả về một con trỏ tới bộ nhớ vừa được cấp phát.
int *p = (int*)malloc(sizeof(int));
Ở đây, p
là một con trỏ tới một số nguyên đã được cấp phát bằng malloc
. Toán tử sizeof
được sử dụng để xác định kích thước của một số nguyên tính theo byte.
Sau khi cấp phát bộ nhớ, bạn có thể sử dụng biến con trỏ như với bất kỳ con trỏ nào khác. Khi sử dụng xong, bạn nên giải phóng bộ nhớ được phân bổ cho p
bằng hàm free
. Ví dụ: free(p);
Chuyển con trỏ
Khi cần chuyển một con trỏ từ kiểu này sang kiểu khác, bạn áp dụng cú pháp (type*)
. Ví dụ:
double *p = (double *)malloc(sizeof(double));
Ở ví dụ trên, p
được chuyển thành con trỏ kiểu double
.
Con trỏ trong C là một công cụ mạnh mẽ với lập trình viên ngôn ngữ C. Tính năng con trỏ không chỉ phổ biến mà còn rất hữu ích trong việc lập trình. Thành thạo con trỏ C sẽ giúp bạn thao tác với bộ nhớ cũng như làm việc với các cấu trúc dữ liệu phức tạp dễ dàng hơn.
Cảm ơn bạn đã đọc bài viết, nếu bạn đang quan tâm đến học lập trình online, hãy tham khảo ngay các khóa học lập trình tại ICANTECH nhé!
Nguồn ảnh: ICANTECH.