Xem thêm

Bài 63. Con trỏ trong C

Huy Erick
Trong bài viết này, chúng ta sẽ tìm hiểu về "cách sử dụng con trỏ" trong ngôn ngữ lập trình C. Bài viết này sẽ cung cấp cho bạn một cái nhìn tổng quan về...

Trong bài viết này, chúng ta sẽ tìm hiểu về "cách sử dụng con trỏ" trong lập trình c' class='hover-show-link replace-link-1748'>ngôn ngữ lập trình c . Bài viết này sẽ cung cấp cho bạn một cái nhìn tổng quan về con trỏ, những khái niệm cơ bản liên quan và cách sử dụng con trỏ trong C. Bạn sẽ nhận thấy rằng con trỏ là một phần kiến thức rộng lớn, do đó chúng ta sẽ chỉ tập trung vào các khái niệm cơ bản về con trỏ trong bài viết này. Các bài viết tiếp theo sẽ cung cấp thông tin chi tiết hơn về con trỏ khi làm việc với mảng, cấp phát bộ nhớ và quản lý bộ nhớ,... Hi vọng rằng loạt bài học về con trỏ trong C này sẽ giúp bạn tự tin hơn trong việc sử dụng chúng.

Con Trỏ Trong C Con trỏ trong C là một loại biến đặc biệt mà giá trị của nó là địa chỉ của một biến khác.

Địa chỉ của biến trong C

Để hiểu và sử dụng được con trỏ trong C, trước tiên bạn cần hiểu về khái niệm "địa chỉ" trong C. Nếu bạn đã theo dõi khóa học C đến từ đầu, chắc hẳn bạn đã nghe nhắc đến khái niệm này rồi. Ở phần này, chúng ta sẽ làm sáng tỏ vấn đề này.

int number;  printf("Nhập number = ");  scanf("%d", &number);  printf("number = %d", number); 

Bạn có thể xem ví dụ trên đây. Tại sao khi sử dụng hàm scanf chúng ta cần truyền vào &number, còn hàm printf ta không cần có dấu & kia? Đó là vì nếu bạn muốn nhập giá trị cho biến, hàm scanf cần biết địa chỉ của biến đó trong bộ nhớ.

Mỗi biến mà bạn khai báo đều có địa chỉ riêng của nó và giá trị mà nó đang lưu trữ. Để xem địa chỉ của biến, bạn thêm dấu & vào trước tên biến. Xem xét ví dụ dưới đây:

#include   int main() {      int number = 5;      printf("Giá trị của number = %d", number); // truy xuất địa chỉ bằng cách thêm `&` trước tên biến      printf("Địa chỉ của number = %d", &number);      return 0;  } 

Kết quả khi chạy chương trình:

Giá trị của number = 5  Địa chỉ của number = 6487580 

Chú ý:

  • Bạn có thể nhận được các địa chỉ khác nhau mỗi khi chạy code trên.
  • Để nhận giá trị địa chỉ dưới dạng thập lục phân như trong ảnh ở đầu bài, bạn thay %d bằng %x.

Con trỏ trong C

Con trỏ là gì?

Con trỏ trong C cũng chỉ là một loại biến, có thể khai báo, khởi tạo và lưu trữ giá trị, nhưng nó không lưu giá trị bình thường, mà là "địa chỉ".

Chúng ta cùng thống nhất một số khái niệm khi làm việc với con trỏ:

  • Giá trị của con trỏ: địa chỉ mà con trỏ trỏ tới.
  • Địa chỉ của con trỏ: địa chỉ của biến con trỏ đó.
  • Giá trị của biến mà con trỏ đang trỏ tới.
  • Địa chỉ của biến mà con trỏ đang trỏ tới = giá trị của con trỏ.

Chính vì con trỏ mang địa chỉ, nó là một biến đặc biệt có thêm những quyền năng mà biến bình thường không có. Nhờ việc nó mang địa chỉ, nó có thể trỏ bất kỳ đâu trong bộ nhớ. Điều này là một điểm mạnh nếu ta khai thác tốt, nhưng nếu không quản lý tốt thì nó có thể gây ra vấn đề cho chương trình.

Cách khai báo con trỏ

Con trỏ trong C cũng có thể được khai báo tương tự biến bình thường, tên biến là một định danh hợp lệ. Cú pháp như sau:

*

Trong đó:

  • Kiểu dữ liệu có thể là: void, int, float, double,...
  • Dấu * trước tên biến là ký hiệu báo cho trình biên dịch biết ta đang khai báo con trỏ.
int *p_i; // khai báo con trỏ để trỏ tới biến kiểu nguyên int *p, val; // khai báo con trỏ `p` kiểu int, biến `val` (không phải con trỏ) kiểu int float *p_f; // khai báo con trỏ để trỏ tới biến kiểu thực char *p_char; // khai báo con trỏ để trỏ tới biến kiểu ký tự void *p_v; // con trỏ kiểu void (không kiểu)

Gán giá trị cho con trỏ

Sau khi khai báo con trỏ, bạn cần khởi tạo giá trị cho nó. Nếu con trỏ được sử dụng mà không được khởi tạo, giá trị của nó sẽ là giá trị rác, điều này sẽ làm chương trình của bạn chạy không đúng, thậm chí gây nguy hiểm nếu giá trị rác đó chẳng may lại chính là địa chỉ của một biến bạn đang dùng.

int *p, value; value = 5; p = &value; // khởi tạo giá trị cho con trỏ `p` là địa chỉ của `value`  // Hoặc bạn có thể khai báo và khởi tạo đồng thời: int value = 5; int *p = &value; // khai báo con trỏ `p` và khởi tạo giá trị cho con trỏ là địa chỉ của `value`  // Lưu ý: // - Con trỏ khi khai báo nên được khởi tạo giá trị ngay. // - Con trỏ kiểu void là loại con trỏ tổng quát, nó có thể nhận địa chỉ của biến bất kỳ với bất kỳ kiểu dữ liệu nào.
void *p_int = NULL; printf("Giá trị của con trỏ là %d", p_int); // Output: Giá trị của con trỏ là 0

Bản chất của con trỏ trong C

Bạn sẽ hiểu rõ hơn về quyền năng của con trỏ trong phần này, cùng xem ví dụ dưới đây:

#include   int main() {      // Khai báo + khởi tạo biến `value` = 10      int value = 10;      // Lấy giá trị của biến `value`      printf("Giá trị của `value` = %d", value);      // Lấy địa chỉ của biến `value`      printf("Địa chỉ của `value` = %d", &value);      printf("\n\n");      /* Khai báo + khởi tạo biến con trỏ `p` có giá trị là địa chỉ của biến `value` */      int *p = &value;      // Lấy giá trị của con trỏ `p`      printf("Giá trị của con trỏ `p` = %d", p);      // Lấy địa chỉ của con trỏ `p`      printf("Địa chỉ của con trỏ `p` = %d", &p);      // Lấy giá trị của biến mà con trỏ `p` đang trỏ tới dùng toán tử `*`      printf("Giá trị của biến mà con trỏ `p` đang trỏ tới = %d", *p);      printf("\n\n");      /* Thay đổi giá trị của biến `value` thông qua con trỏ `p`      Giống như hàm `scanf()` có thể thay đổi giá trị của biến khi nhận vào địa chỉ,      con trỏ khi có địa chỉ của một biến hoàn toàn có thể thay đổi giá trị của biến đó     theo cách dưới đây: */      // Lấy giá trị của biến `value`      printf("Giá trị của `value` = %d", value);      // Thay đổi giá trị của biến `value` thông qua `p`      *p = 100;      // Lấy giá trị của biến `value`      printf("Giá trị của `value` = %d", value);      // Lấy giá trị của biến mà con trỏ `p` đang trỏ tới dùng toán tử `*`      printf("Giá trị của biến mà con trỏ `p` đang trỏ tới = %d", *p);      printf("\n\n");      /* Việc lấy giá trị của biến thông qua con trỏ chỉ là một cách khác để lấy giá trị của biến đó. */      value = 1000;      // Lấy giá trị của biến `value`      printf("Giá trị của `value` = %d", value);      // Lấy giá trị của biến mà con trỏ `p` đang trỏ tới dùng toán tử `*`      printf("Giá trị của biến mà con trỏ `p` đang trỏ tới = %d", *p);  } 

Kết quả chạy:

Giá trị của `value` = 10 Địa chỉ của `value` = 6487580 - Giá trị của con trỏ `p` = 6487580 Địa chỉ của con trỏ `p` = 6487568 Giá trị của biến mà con trỏ `p` đang trỏ tới = 10 - Giá trị của `value` = 10 Giá trị của `value` = 100 Giá trị của biến mà con trỏ `p` đang trỏ tới = 100 - Giá trị của `value` = 1000 Giá trị của biến mà con trỏ `p` đang trỏ tới = 1000

Qua ví dụ này, bạn có thể thấy rõ các kết luận sau về con trỏ:

  • Địa chỉ của biến value chính là giá trị của con trỏ p, cả hai đều là 6487580. Lưu ý rằng giá trị địa chỉ này có thể khác nhau mỗi lần chạy.
  • Con trỏ có thể lấy giá trị của biến mà nó đang trỏ tới bằng toán tử *: printf("Giá trị của biến mà con trỏpđang trỏ tới = %d", *p);
  • Con trỏ có thể thay đổi giá trị của biến mà nó đang trỏ tới. Do nó mang địa chỉ của biến, khi đó nó hoàn toàn có quyền thay đổi giá trị của biến đó. Như trong ví dụ trên, ta đã thay đổi giá trị từ 10 lên 100.

Bài học hôm nay chúng ta sẽ chỉ dừng lại ở những kiến thức trên, các bài học sau chúng ta sẽ đi tìm hiểu về mối liên hệ giữa con trỏ với mảng và con trỏ với hàm, cũng như cách quản lý bộ nhớ khi làm việc với con trỏ trong C.

Các lỗi thường gặp khi làm việc với con trỏ

Giả sử bạn muốn khởi tạo giá trị của con trỏ p để trỏ tới địa chỉ của biến value, khi đó:

int value, *p; // Sai! `p` cần địa chỉ, `value` không phải là cái địa chỉ đó. p = value; // Sai! `*p` là giá trị của biến mà con trỏ đang trỏ tới, `&value` là địa chỉ. *p = &value; // Đúng rồi! `p` cần một địa chỉ, `&value` là địa chỉ của biến `value`. p = &value; // Đúng! `*p` là giá trị của biến mà con trỏ đang trỏ tới, và `c` cũng là giá trị (không phải địa chỉ). *p = value;

Các bạn khi mới học con trỏ sẽ bối rối về dấu * ở phần khai báo và khi lấy giá trị của biến mà con trỏ đang trỏ tới:

#include  int main() {     int c = 5;     // Dấu `*` ở đây để chúng ta biết chúng ta đang khai báo con trỏ.     // Không phải lấy giá trị của nó nhé     int *p = &c;     // Khai báo trên tương đương     // int *p;     // p = &c;      // Nếu bạn muốn phân biệt 2 thằng này, khi khai báo có thể viết như sau:     // int* p = &c;      // Lấy giá trị chỉ của biến mà con trỏ đang trỏ tới, chính là giá trị của `c`     printf("%d", *p); // 5 }

Tài liệu tham khảo

Mặc dù mình đã cố gắng trình bày tỉ mỉ, nhưng có thể còn thiếu sót. Dưới đây là một số tài liệu bạn nên đọc thêm để hiểu rõ hơn về con trỏ trong C:

  1. Tìm hiểu bản chất của con trỏ từ cơ bản tới nâng cao.
  2. C Pointers (With Example).
1