Bỏ qua để đến nội dung

Con trỏ hằng (const pointer)

Từ khóa const có thể kết hợp với con trỏ theo nhiều cách khác nhau, mỗi cách mang một ý nghĩa riêng. Đây là chủ đề khiến nhiều người mới học C++ bối rối, nhưng chỉ cần nhớ một quy tắc đơn giản: const áp dụng cho phần đứng ngay bên trái nó.

1. Con trỏ trỏ đến giá trị hằng (pointer to const)

Phần tiêu đề “1. Con trỏ trỏ đến giá trị hằng (pointer to const)”

Giá trị tại địa chỉ mà con trỏ trỏ đến không thể bị thay đổi thông qua con trỏ này - nhưng bản thân con trỏ vẫn có thể trỏ sang địa chỉ khác:

#include <iostream>
int main() {
int x = 10;
int y = 20;
const int* ptr = &x; // "con trỏ trỏ đến một int hằng"
// *ptr = 100; // LỖI! Không thể thay đổi giá trị thông qua ptr
ptr = &y; // OK - ptr có thể trỏ sang biến khác
std::cout << *ptr << std::endl; // 20
return 0;
}

Ngược lại, con trỏ không thể trỏ sang địa chỉ khác sau khi khởi tạo - nhưng giá trị tại địa chỉ đó vẫn có thể thay đổi:

#include <iostream>
int main() {
int x = 10;
int y = 20;
int* const ptr = &x; // "con trỏ hằng, trỏ đến một int"
*ptr = 100; // OK - có thể thay đổi giá trị tại địa chỉ ptr đang trỏ
// ptr = &y; // LỖI! Không thể đổi hướng trỏ của một con trỏ hằng
std::cout << x << std::endl; // 100
return 0;
}

3. Con trỏ hằng trỏ đến giá trị hằng

Phần tiêu đề “3. Con trỏ hằng trỏ đến giá trị hằng”

Kết hợp cả hai: vừa không thể đổi hướng trỏ, vừa không thể thay đổi giá trị tại đó:

int x = 10;
const int* const ptr = &x; // Cả con trỏ VÀ giá trị đều là hằng
// *ptr = 100; // LỖI!
// ptr = &y; // LỖI!
std::cout << *ptr << std::endl; // 10 - chỉ có thể đọc

Một mẹo hữu ích để phân biệt các khai báo trên: đọc khai báo từ tên biến, lùi dần sang trái:

Khai báo Đọc là Ý nghĩa
int* ptr “ptr là con trỏ đến int” Con trỏ bình thường
const int* ptr “ptr là con trỏ đến int hằng” Không đổi được giá trị
int* const ptr “ptr là con trỏ hằng, đến int” Không đổi được hướng trỏ
const int* const ptr “ptr là con trỏ hằng, đến int hằng” Không đổi được cả hai

Cách dùng const với con trỏ phổ biến nhất là trong tham số hàm, để đảm bảo hàm không vô tình sửa đổi dữ liệu được trỏ đến:

#include <iostream>
void print_value(const int* ptr) {
std::cout << *ptr << std::endl;
// *ptr = 100; // LỖI! Hàm này chỉ được phép đọc, không được sửa
}
int main() {
int x = 42;
print_value(&x);
return 0;
}
  • const int* ptr: giá trị tại địa chỉ trỏ đến không thể sửa, nhưng con trỏ có thể đổi hướng
  • int* const ptr: con trỏ không thể đổi hướng, nhưng giá trị tại đó vẫn sửa được
  • const int* const ptr: cả hai đều không thể thay đổi
  • Dùng const với con trỏ trong tham số hàm là cách phổ biến để đảm bảo hàm không sửa đổi dữ liệu ngoài ý muốn