Giới thiệu về con trỏ trong C, cách tạo và quản lý con trỏ C

Một trong những điểm khác biệt giữa C và ngôn ngữ khác như Java. Đó là nó cho phép thao tác trực tiếp với một thứ gọi là “con trỏ”. Con trỏ được hiểu là tham chiếu đến không gian bộ nhớ cụ thể. Cho phép điều khiển và quản lý bộ nhớ trực tiếp. Nhiều lập trình viên chuyển sang C và sử dụng các con trỏ. Vậy cụ thể con trỏ trong C được sử dụng như thế nào thì cùng Box.edu khám phá thêm nhé!

Xem thêm: Hướng dẫn cách sử dụng danh sách liên kết trong C hiệu quả

Xem thêm: Hướng dẫn cách tìm nạp chuỗi trong hàm C fgets chính xác

Xem thêm: Cách đơn giản để chuyển đổi sáng giá trị số – C String to Int

Mục lục bài viết

Tổng quan về con trỏ trong C

tong-quan-ve-con-tro-trong-c-1.jpg

Tổng quan về con trỏ trong C

Khái niệm chung

Khi một người hình thành bộ nhớ mới, bộ nhớ này sẽ được lưu trữ vật lý thông qua mạng lưới thần kinh của người đó. Quá trình này tạo ra các kết nối mới giữa các tế bào thần kinh của người này. 

Còn khi xử lý với máy tính, khả năng truy cập trực tiếp của các ký ức cá nhân sẽ dễ hơn một chút. Mỗi phần dữ liệu mà máy tính lưu trữ đều nằm trong một địa chỉ bộ nhớ cụ thể. Đồng thời “con trỏ” cũng cung cấp tham chiếu đến các vị trí này.

Trước đây việc sử dụng con trỏ rất đơn giản. Ví dụ: một hệ thống chỉ có 54 phần dữ liệu tiềm năng thì có thể trỏ đến 1 đến 54. Do đó một phần dữ liệu có thể ở khoảng trống “3” hoặc khoảng trắng “53”. Ngày nay, việc cấp phát bộ nhớ cho các chương trình phức tạp hơn nhiều.

Trong ngôn ngữ C, con trỏ thường được sử dụng để quản lý bộ nhớ trực tiếp. Chương trình chạy càng lâu thì càng sử dụng nhiều bộ nhớ hơn cho đến khi chương trình có thể gặp sự cố. Bằng cách sử dụng con trỏ, các nhà phát triển có thể kiểm soát trực tiếp dữ liệu mà họ lưu trữ – thao tác và giải phóng không gian dữ liệu nếu cần.

Rủi ro khi sử dụng con trỏ trong C

Ngoài những lợi ích khi sử dụng con trỏ mang lại thì đi kèm với nó là một vài rủi ro có thể xảy ra. Cụ thể con trỏ có thể hỗn loạn và khó sử dụng về mặt lập trình, bởi vì chúng rất gần với lớp máy của mã, chúng vừa mạnh mẽ vừa nguy hiểm.

C được tạo ra vào năm 1972, do đó hiện nay các ngôn ngữ lập trình hiện đại sẽ cung cấp mức độ tự động hóa và trừu tượng cao hơn so với C. Chúng thực hiện tất cả việc cấp phát, quản lý và xử lý bộ nhớ cho nhà phát triển. Mặc dù điều này khiến nhà phát triển mất một số quyền kiểm soát, nhưng nó nâng cao tính nhất quán và ổn định của sản phẩm cuối cùng.

Ngoài ra các lập trình viên mới có thể phân bổ hoặc thao tác sai bộ nhớ, dẫn đến các vấn đề về bảo mật và ổn định. Nếu một lập trình viên không hiểu sâu về cách thức hoạt động của con trỏ, nó có thể dẫn đến một chương trình phức tạp và không ổn định. Nếu lập trình viên không nhận xét đúng về mã của họ, nó có thể trở nên khó đọc và không thể hiểu được.

Tuy nhiên, các lập trình viên C cũng cần phải học về con trỏ và quản lý bộ nhớ trước khi họ có thể phát triển các chương trình lớn hơn. Đối với chương trình lớn hơn, quản lý bộ nhớ có thể là một khía cạnh quan trọng để đảm bảo tính ổn định của nền tảng – nó đòi hỏi các lập trình viên phải cực kỳ tận tâm và chi tiết. 

Cách tạo con trỏ trong C

Bất kỳ khi nào bạn tạo một biến trong C, nó sẽ tạo ra một địa chỉ bộ nhớ. Mỗi biến mà bạn tạo không chỉ có kiểu mà còn có vị trí. 

#include <stdio.h>

int main() {
	int var1;
}

Ở ví dụ này chúng tôi đã tạo một “số nguyên” được gọi là “var1”. Nó trống rỗng và chúng tôi thậm chí đã không làm bất kỳ điều gì với nó. Nhưng bất chấp điều đó, vì chúng ta đã khai báo số nguyên, nên nó đã có một địa chỉ bộ nhớ.

Vậy làm thế nào để chúng tôi truy cập địa chỉ được lưu trữ này? Bằng cách sử dụng & (dấu và) ở phía trước của biến, cho phép in nó ra dưới dạng chuỗi thông thường trong C. Hãy in địa chỉ bộ nhớ như sau:

int main() {
		int var1;
		printf(“%x”,&var1);
	}

“% X” là phương thức sử dụng “printf ()” để in một chuỗi chứa vị trí bộ nhớ này. Trong đó bộ nhớ được cấp phát sẽ giống như “0x7fff9575c05f”. Địa chỉ bộ nhớ ngày càng trở nên dài và phức tạp hơn. Nhưng điều đó không quan trọng. Vì chúng ta không cần theo dõi các bộ nhớ được cấp phát theo cách thủ công. Đó là những gì biến con trỏ thực hiện.

Do đó bạn không cần phải khai báo trực tiếp một con trỏ. Ở ví dụ này, con trỏ int tự tạo – và điều đó sẽ đúng với bất kỳ kiểu dữ liệu nào, không chỉ kiểu int. Ban đầu bạn sẽ có thể tự động gán địa chỉ và dấu & được kết hợp với hàm printf () sẽ in ra địa chỉ. Chỉ cần bạn biết cú pháp con trỏ cơ bản này, bạn có thể làm được nhiều điều khác.

Tuy nhiên việc không cần khai báo trực tiếp một con trỏ không có nghĩa là bạn sẽ không bao giờ cần. Có thể khai báo trực tiếp một con trỏ cụ thể:

int *newPointer = malloc(sizeof(int));

Điều này sẽ tạo ra một con trỏ mới trong “newPointer” có kích thước là một số nguyên. Sau đó, bạn có thể thay đổi, tham chiếu và xóa biến con trỏ này. 

Cách thay đổi một giá trị con trỏ trong C

cach-thay-doi-mot-gia-tri-con-tro-trong-c.jpg

Cách thay đổi một giá trị con trỏ trong C

Có hai điều bạn có thể làm trong C đó là: thay đổi giá trị của một con trỏ và gán lại con trỏ cho một địa chỉ khác. Mặc dù điều này có vẻ giống nhau, nhưng chúng thực sự rất khác nhau. Hãy coi “con trỏ” là cánh cửa mà bạn mở ra và giá trị của con trỏ là những gì đằng sau cánh cửa đó.

Việc thay đổi giá trị của một con trỏ không khác gì việc thay đổi giá trị của biến một cách đơn giản. Bởi vì biến sẽ vẫn được lưu trữ ở nơi mà biến được lưu trữ. 

Int newPointer = malloc(sizeof(int));
newPointer = 0;
printf(newPointer);
printf(“\n”);
printf(*newPointer);
printf(“\n”);
newPointer = 1;
printf(newPointer);
printf(“\n”);
printf(*newPointer);

Bạn sẽ thấy rằng con trỏ newPointer hoàn toàn không thay đổi, mặc dù giá trị thì có. Các kết quả có thể sẽ tương tự như:

0
0x7fff9575c05f
1
0x7fff9575c05f

Giá trị mà con trỏ đang tham chiếu sẽ luôn thay đổi. Quan trọng là làm thế nào để thay đổi chính các con trỏ này.

Cách gán lại một con trỏ trong C cho một địa chỉ khác

Con trỏ không thay đổi khi giá trị thay đổi. Nhưng một công tắc con trỏ có thể thay đổi nơi con trỏ trỏ đến. Hãy quay lại với ví dụ newPointer:

int newPointer = 1;

“* NewPointer” là một vị trí bộ nhớ, nhưng “newPointer” (không có dấu hoa thị) chỉ đơn giản là một biến Ngay bây giờ, “* newPointer” trỏ đến một vị trí bộ nhớ và vị trí bộ nhớ đó được đọc “1”.

Tiếp theo sẽ gán giá trị:

int firstPointer = 1;
int secondPointer = 2;

Biến Pointer đầu tiên cho biết “1” và một biến Pointer thứ hai cho biết “2”. Nhưng chúng ta có thể thay đổi điều đó bằng đoạn mã dưới đây:

*firstPointer = *secondPointer;

Như vậy là bạn đã cung cấp cho “firstPointer” địa chỉ của một biến khác.

Đầu tiên, không có giá trị nào thay đổi. Điều đã xảy ra là firstPointer bây giờ trỏ đến cùng một địa chỉ bộ nhớ với secondPointer. Khi bạn gọi firstPointer tiếp theo, nó sẽ đọc “2” chứ không phải “1”.

Tiếp theo sẽ không còn tồn tại một con trỏ tới không gian bộ nhớ tham chiếu đến “1”. Điều này là không tốt vì bộ nhớ không được giải phóng. Nó chỉ đơn giản là không tham chiếu. Vì có thể tham chiếu đến các con trỏ bằng cách sử dụng dấu hoa thị (*). Nên có thể thao tác chúng giống với bất kỳ biến nào khác. Nhưng điều này có thể nguy hiểm vì bạn không trực tiếp thay đổi giá trị của bất kỳ biến nào. Mà bạn đang thay đổi nơi một biến nhất định trỏ đến. Int ptr đầu tiên không tồn tại và biến số nguyên đã thay đổi. Nhưng bản thân số nguyên vẫn được lưu trữ ở đâu đó.

Cách sử dụng hàm con trỏ trong C

Ngoài việc sử dụng con trỏ đến các biến, bạn cũng có thể sử dụng con trỏ đến các hàm. Nhưng trong khi một con trỏ biến trỏ đến một giá trị được lưu trữ. Một con trỏ hàm trỏ đến mã của hàm.

Với con trỏ hàm, bạn sẽ không cấp phát hay giải phóng bộ nhớ. Bạn chỉ cần trỏ đến một vị trí trong mã mà hàm đã có sẵn. Nếu không, các hàm được xem như một con trỏ biến, với dấu hoa thị ở trước tên hàm.

Có thể sử dụng con trỏ hàm thay vì “trường hợp chuyển mạch”. Bởi vì con trỏ hàm theo nghĩa đen trỏ đến hàm được thiết kế để sử dụng. Ví dụ: bạn có thể xây dựng một mảng con trỏ hàm từ các con trỏ đến các hàm mà bạn muốn chuyển qua. Tuy nhiên, chúng thường được coi là hình thức xấu. Không phải vì nó không thể sử dụng được, mà do nó không dễ đọc. Sẽ khó cho một lập trình viên khác đọc mã để có thể hiểu được.

Bạn sẽ khai báo một con trỏ hàm như sau:

int (*functionPointer)(int);

Khi khai báo một con trỏ hàm, bạn phải đặt nó trong dấu ngoặc đơn. Nếu không, bạn có thể coi functionPointer như một loại con trỏ khác.

Tổng kết

Thông tin về con trỏ trong C được Box.edu trình bày ở trên mong rằng sẽ là những thông tin bổ ích và quan trọng để các nhà lập trình viên có thể nắm rõ hơn về con trỏ và vận dụng nó vào các ngôn ngữ lập trình khác nhau.

4.8/5 - (5 bình chọn)
Xin chào các bạn! Mình là Hà Nguyên Phương Hiện đang là tác giả của Box.edu.vn website nổi tiếng chuyên review, đánh giá, giới thiệu các khóa học hay nhất hiện nay.