Chuyển đến nội dung

Pointers: Addresses, Dereferencing & Arithmetic

Understand pointers as memory addresses, learn address-of and dereference operators, pointer arithmetic within arrays, const correctness with pointers, and when raw pointers are still appropriate in modern C++.

What Is a Pointer?

A pointer is a variable that stores a memory address. Every object in C++ lives at some address in memory, and a pointer lets you refer to that object indirectly. Pointers are fundamental to C++ — they enable dynamic data structures, polymorphism, and efficient parameter passing.

A pointer type is declared with*:int* p;declares a pointer to anint. The pointer itself occupies memory (typically 8 bytes on 64-bit systems) and its value is an address, not the data it points to.

Address-Of & Dereference

Hai toán tử con trỏ cơ bản là&(địa chỉ của) và*(dereference). Toán tử&lấy địa chỉ của một biến. Toán tử*tuân theo địa chỉ để truy cập đối tượng được trỏ tới.

address_deref.cpp
#include <iostream>

int main() {
    int value = 42;
    int* ptr = &value;  // ptr holds the address of value

    std::cout << "value:  " << value << '\n';        // 42
    std::cout << "&value: " << &value << '\n';       // e.g. 0x7ffd5a3c
    std::cout << "ptr:    " << ptr << '\n';           // same address
    std::cout << "*ptr:   " << *ptr << '\n';          // 42 (dereference)

    *ptr = 100;  // modify value through the pointer
    std::cout << "value after *ptr = 100: " << value << '\n'; // 100

    int* null_ptr = nullptr;  // points to nothing — safe sentinel
    // *null_ptr = 5;  // UNDEFINED BEHAVIOR — crash on most systems
}

Số học con trỏ và phân rã mảng

Số học con trỏ chỉ hợp lệ trong một mảng (hoặc một mảng ở cuối mảng). Trình biên dịch chia tỷ lệ số học theosizeof(T). Tên mảng phân rã thành con trỏ tới phần tử đầu tiên của nó trong hầu hết các ngữ cảnh — đây là lý do tại sao mảng kiểu C mất thông tin kích thước khi được truyền cho hàm.

pointer_arithmetic.cpp
#include <iostream>
#include <cstddef>  // std::ptrdiff_t

int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    int* p = arr;  // array decays to pointer to first element

    std::cout << *p << '\n';        // 10
    std::cout << *(p + 2) << '\n';  // 30  (moves 2*sizeof(int) bytes)
    std::cout << p[3] << '\n';      // 40  (p[i] is *(p + i))

    // Iterating via pointer
    for (int* it = arr; it != arr + 5; ++it) {
        std::cout << *it << ' ';
    }
    std::cout << '\n';  // 10 20 30 40 50

    // Pointer difference
    std::ptrdiff_t diff = (arr + 4) - arr;  // 4 (elements, not bytes)
    std::cout << "diff: " << diff << '\n';

    // WARNING: arithmetic outside the array is UNDEFINED BEHAVIOR
    // int* bad = arr + 10;  // UB — don't do this
}

const Con trỏ vs Con trỏ tới const

Vị trí củaconstso với*xác định điều gì là bất biến:

- const int* p (hoặcint const* p) — con trỏ tới const: bạn không thể sửa đổi giá trị được trỏ tới thông quap, nhưng bạn có thể đặt lạipđể trỏ đến nơi khác.
- int* const p — con trỏ const: bản thân con trỏ là bất biến (không thể đặt lại vị trí), nhưng bạn có thể sửa đổi giá trị mà nó trỏ tới.
- const int* const p — cả con trỏ và giá trị được trỏ tới đều không thể thay đổi.

Đọc các khai báo từ phải sang trái:int* const pđọc là "plà con trỏ hằng tới int."

Con trỏ trống (void*) có thể chứa bất kỳ địa chỉ nào nhưng không thể hủy đăng ký nếu không truyền. Chúng chủ yếu được sử dụng trong triển khai API kiểu C và bộ cấp phát.

Thực hành tốt

Trong C++ hiện đại, con trỏ thô chỉ nên được sử dụng dưới dạng con trỏ quan sát, không sở hữu. Họ nói: "Tôi có thể nhìn thấy vật thể này, nhưng tôi không chịu trách nhiệm về cuộc đời của nó". Sử dụng hợp pháp phổ biến:

- Triển khai cấu trúc dữ liệu (các nút cây trỏ đến nút cha)
- Tham số chức năng quan sát nhưng không sở hữu (mặc dù tài liệu tham khảo thường tốt hơn)
- Giao diện với API C
- Trình vòng lặp trong vùng chứa tùy chỉnh

Nếu một con trỏ sở hữu một tài nguyên, hãy gói nó trongstd::unique_ptrhoặcstd::shared_ptr. Nếu bạn thấy mình đang viếtdelete, bạn gần như chắc chắn đã làm sai trong C++ hiện đại.

Lỗi thường gặp

Con trỏ lơ lửng là lỗi con trỏ số 1. Một con trỏ trở nên lơ lửng khi đối tượng mà nó trỏ tới bị phá hủy:

- Trả về con trỏ tới biến cục bộ
- Xóa một đối tượng trong khi con trỏ khác vẫn tham chiếu đến nó
- Con trỏ vàostd::vectorđược phân bổ lại saupush_back

Con trỏ chưa được khởi tạo chứa địa chỉ rác. Luôn khởi tạonullptrnếu bạn chưa có mục tiêu. Trình biên dịch sẽ KHÔNG cảnh báo về tất cả việc sử dụng con trỏ chưa được khởi tạo.

Con trỏ và tham chiếu: Khi nào nên sử dụng cái nào

Tài liệu tham khảo và con trỏ đều cung cấp thông tin gián tiếp nhưng chúng khác nhau ở những điểm quan trọng. Các tham chiếu không được rỗng, không được đặt lại vị trí và không yêu cầu hội thảo rõ ràng. Thích tham khảo các tham số hàm; sử dụng con trỏ khi cần vô hiệu hóa hoặc sắp xếp lại chỗ ngồi.

ptr_vs_ref.cpp
#include <iostream>

void increment_ref(int& val) { ++val; }         // cannot be null
void increment_ptr(int* val) { if (val) ++(*val); } // must null-check

int main() {
    int x = 10;
    increment_ref(x);        // clean call syntax
    increment_ptr(&x);       // caller must take address
    std::cout << x << '\n';  // 12

    int* p = &x;
    p = nullptr;  // OK — pointers can be re-seated and nulled

    int& r = x;
    // r = ???;    // references cannot be re-seated
    // int& bad;   // ERROR — references must be initialized
}
Điểm chính
  • &nhận địa chỉ,*theo sau địa chỉ đó - đây là các thao tác nghịch đảo
  • Số học con trỏ chỉ hợp lệ trong mảng và tỷ lệ bằngsizeof(T)
  • Tên mảng phân rã thành con trỏ, mất thông tin kích thước
  • Đọc vị tríconsttừ phải sang trái:const int*so vớiint* const
  • Trong C++ hiện đại, con trỏ thô chỉ nên là người quan sát không sở hữu
  • Thích tham chiếu hơn con trỏ trừ khi bạn cần tính chất vô hiệu hoặc đặt lại chỗ

Câu đố — Kiểm tra kiến thức

(15 XP)

1. `const int* p`có nghĩa là gì?

2. What happens when an array is passed to a function expecting a pointer parameter?

3. When is pointer arithmetic valid in C++?