Giao diện
🎯 Pointers & References — The Beast
Đây là phần QUAN TRỌNG NHẤT của C++. Nếu bạn hiểu Pointers và References, bạn hiểu C++. Nếu không, bạn sẽ mãi đau khổ với bugs khó hiểu.
Khái niệm cơ bản: Địa chỉ bộ nhớ
Mỗi biến trong bộ nhớ có một địa chỉ — vị trí cụ thể trong RAM:
cpp
#include <iostream>
int main() {
int x = 42;
std::cout << "Giá trị của x: " << x << std::endl;
std::cout << "Địa chỉ của x: " << &x << std::endl;
return 0;
}Output:
Giá trị của x: 42
Địa chỉ của x: 0x7ffd5c3b3a4cVisualization:
RAM
┌─────────────────────┐
0x...3a48│ ... │
├─────────────────────┤
0x...3a4c│ 42 │ ← x lives here (4 bytes for int)
├─────────────────────┤
0x...3a50│ ... │
└─────────────────────┘Pointer: Biến chứa địa chỉ
Pointer là biến đặc biệt — giá trị của nó là địa chỉ của biến khác:
cpp
#include <iostream>
int main() {
int x = 42;
// Khai báo pointer: type* name
int* ptr = &x; // ptr chứa địa chỉ của x
std::cout << "x = " << x << std::endl; // 42
std::cout << "&x = " << &x << std::endl; // 0x7ffd...
std::cout << "ptr = " << ptr << std::endl; // 0x7ffd... (same as &x)
std::cout << "*ptr = " << *ptr << std::endl; // 42 (dereference)
return 0;
}Visualization:
┌──────────────────────────────────────────────────────────────┐
│ STACK │
├──────────────────────────────────────────────────────────────┤
│ │
│ x ┌────────┐ │
│ (int) │ 42 │ ← Địa chỉ: 0x7ffd3a4c │
│ └────────┘ │
│ ▲ │
│ │ ptr trỏ đến x │
│ │ │
│ ptr ┌────────────────┐ │
│ (int*) │ 0x7ffd3a4c │ ← ptr chứa địa chỉ của x │
│ └────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘Hai toán tử quan trọng: & và *
| Toán tử | Tên | Ý nghĩa | Ví dụ |
|---|---|---|---|
& | Address-of | Lấy địa chỉ của biến | &x → địa chỉ của x |
* | Dereference | Lấy giá trị tại địa chỉ | *ptr → giá trị mà ptr trỏ đến |
cpp
#include <iostream>
int main() {
int x = 100;
int* ptr = &x;
// Thay đổi giá trị thông qua pointer
*ptr = 200;
std::cout << "x sau khi *ptr = 200: " << x << std::endl; // 200!
return 0;
}🔑 Nguyên tắc vàng
&variable→ "Cho tôi địa chỉ của variable"*pointer→ "Cho tôi giá trị tại địa chỉ mà pointer chứa"
Reference: Bí danh cho biến
Reference là alias — tên khác cho cùng một biến:
cpp
#include <iostream>
int main() {
int x = 42;
int& ref = x; // ref là alias của x
std::cout << "x = " << x << std::endl; // 42
std::cout << "ref = " << ref << std::endl; // 42
std::cout << "&x = " << &x << std::endl; // 0x...
std::cout << "&ref = " << &ref << std::endl; // 0x... (SAME!)
// Thay đổi ref = thay đổi x
ref = 100;
std::cout << "x sau ref = 100: " << x << std::endl; // 100
return 0;
}So sánh với Pointer:
| Đặc điểm | Pointer (int*) | Reference (int&) |
|---|---|---|
| Có thể null? | ✅ (nullptr) | ❌ Phải bind khi khai báo |
| Có thể rebind? | ✅ ptr = &y; | ❌ Một lần là mãi mãi |
| Syntax để truy cập | *ptr | Trực tiếp ref |
| Có thể là member? | ✅ | ⚠️ Phải khởi tạo trong constructor |
cpp
int x = 10, y = 20;
// Pointer có thể rebind
int* ptr = &x;
ptr = &y; // ✅ OK, ptr giờ trỏ đến y
// Reference KHÔNG thể rebind
int& ref = x;
ref = y; // ⚠️ Đây là gán giá trị! x = 20, ref vẫn alias của xPass by Value vs Pointer vs Reference
cpp
#include <iostream>
// Pass by Value — nhận BẢN SAO
void byValue(int n) {
n = 999; // Chỉ thay đổi bản sao local
}
// Pass by Pointer — nhận ĐỊA CHỈ
void byPointer(int* ptr) {
*ptr = 777; // Thay đổi gốc thông qua dereference
}
// Pass by Reference — nhận ALIAS
void byReference(int& ref) {
ref = 555; // Thay đổi gốc trực tiếp
}
int main() {
int a = 100, b = 100, c = 100;
byValue(a);
std::cout << "After byValue: a = " << a << std::endl; // 100 (unchanged)
byPointer(&b);
std::cout << "After byPointer: b = " << b << std::endl; // 777
byReference(c);
std::cout << "After byReference: c = " << c << std::endl; // 555
return 0;
}📌 HPN Best Practice: Khi nào dùng gì?
| Situation | Recommendation |
|---|---|
| Không cần modify, primitive nhỏ | Pass by value |
| Không cần modify, object lớn | const T& (const reference) |
| Cần modify | T& (reference) |
| Có thể null / optional | T* (pointer) + nullptr check |
| Arrays | Pointer (decay to pointer) |
nullptr vs NULL (Modern C++)
cpp
#include <iostream>
void process(int n) {
std::cout << "int version: " << n << std::endl;
}
void process(int* ptr) {
std::cout << "pointer version" << std::endl;
}
int main() {
// ❌ NULL có thể gây ambiguity
// process(NULL); // Có thể gọi int version! (NULL = 0)
// ✅ nullptr luôn là pointer type
process(nullptr); // Luôn gọi pointer version
int* ptr = nullptr; // Modern way to init null pointer
if (ptr == nullptr) {
std::cout << "ptr is null" << std::endl;
}
// Also works with if(!ptr)
if (!ptr) {
std::cout << "ptr is null (boolean check)" << std::endl;
}
return 0;
}⚠️ Luôn dùng nullptr thay vì NULL
NULL được định nghĩa là 0 (số nguyên) trong nhiều compilers, gây ambiguity với overloaded functions.
Pointer Arithmetic (cho Arrays)
cpp
#include <iostream>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int* ptr = arr; // ptr trỏ đến arr[0]
std::cout << "=== Pointer Arithmetic ===" << std::endl;
for (int i = 0; i < 5; ++i) {
// Hai cách truy cập tương đương:
std::cout << "arr[" << i << "] = " << arr[i];
std::cout << " | *(ptr+" << i << ") = " << *(ptr + i) << std::endl;
}
// ptr++ di chuyển sizeof(int) = 4 bytes
ptr++; // ptr giờ trỏ đến arr[1]
std::cout << "\nAfter ptr++: *ptr = " << *ptr << std::endl; // 20
return 0;
}Visualization:
arr: ┌────┬────┬────┬────┬────┐
│ 10 │ 20 │ 30 │ 40 │ 50 │
└────┴────┴────┴────┴────┘
Address: 0x100 0x104 0x108 0x10c 0x110
↑
ptr (ban đầu)
After ptr++:
┌────┬────┬────┬────┬────┐
│ 10 │ 20 │ 30 │ 40 │ 50 │
└────┴────┴────┴────┴────┘
↑
ptr (sau ptr++)Dangling Pointer & Memory Safety
cpp
#include <iostream>
// ❌ NGUY HIỂM: Trả về pointer đến local variable!
int* dangerous() {
int local = 42;
return &local; // local bị destroy khi function ends!
}
// ✅ AN TOÀN: Cấp phát trên heap
int* safe() {
int* heapVar = new int(42);
return heapVar; // Caller phải delete!
}
int main() {
// ❌ Dangling pointer
int* bad = dangerous();
// *bad là undefined behavior — có thể crash, có thể ra số rác
// ✅ Heap allocation
int* good = safe();
std::cout << "*good = " << *good << std::endl; // 42
delete good; // Don't forget!
good = nullptr; // Good practice
return 0;
}☠️ Dangling Pointer
Pointer trỏ đến vùng nhớ đã bị giải phóng. Truy cập sẽ gây Undefined Behavior — chương trình có thể crash, hoặc tệ hơn, chạy sai âm thầm!
🐛 Spot the Bug: Pointer Edition
🐛 Bug Hunt Challenge #1
Chương trình sau sẽ in ra gì?
cpp
#include <iostream>
void swap(int* a, int* b) {
int* temp = a;
a = b;
b = temp;
}
int main() {
int x = 10, y = 20;
swap(&x, &y);
std::cout << "x = " << x << ", y = " << y << std::endl;
return 0;
}💡 Gợi ý
Chúng ta đang swap pointer values hay swap giá trị mà pointer trỏ đến?
🔍 Giải thích & Fix
Output: x = 10, y = 20 — Không swap được!
Bug: Hàm swap đang swap bản sao của pointers (parameters), không phải giá trị.
Fix:
cpp
void swap(int* a, int* b) {
int temp = *a; // Dereference!
*a = *b;
*b = temp;
}Hoặc dùng references (cleaner):
cpp
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
// Gọi: swap(x, y); — không cần &🐛 Bug Hunt Challenge #2
Chương trình sau có bug gì?
cpp
#include <iostream>
int main() {
int* ptr; // Uninitialized!
*ptr = 42;
std::cout << *ptr << std::endl;
return 0;
}🔍 Giải thích
Bug: ptr chưa được khởi tạo — nó chứa garbage value (địa chỉ ngẫu nhiên).
*ptr = 42 ghi vào vùng nhớ random → Segmentation Fault hoặc corrupt memory!
Fix:
cpp
int x;
int* ptr = &x; // Trỏ đến biến hợp lệ
*ptr = 42;
// Hoặc:
int* ptr = new int(42); // Cấp phát heap
// ... use ptr
delete ptr;📚 Tổng kết
| Concept | Key Takeaway |
|---|---|
Pointer (int*) | Biến chứa địa chỉ, dùng * để dereference |
Reference (int&) | Alias, phải bind khi khai báo, không rebind |
&variable | Address-of operator — lấy địa chỉ |
*pointer | Dereference — lấy giá trị tại địa chỉ |
nullptr | Modern null pointer (thay NULL) |
| Pass by pointer/ref | Cho phép modify biến gốc |
| Dangling pointer | ☠️ Trỏ đến memory đã bị free — UB! |
➡️ Tiếp theo
Bạn đã hiểu Pointers & References! Tiếp theo: Input/Output — Giao tiếp với người dùng qua std::cin/cout.