Skip to content

L9. Con Trỏ Cơ Bản (Pointer)¤

1. Biến và Vùng Nhớ¤

1.1. Bộ Nhớ Máy Tính¤

Cấu trúc bộ nhớ RAM: - Bộ nhớ RAM chứa rất nhiều ô nhớ - Mỗi ô nhớ có kích thước 1 byte - Mỗi ô nhớ có địa chỉ duy nhất - Địa chỉ được đánh số từ 0 trở đi

Text Only
Memory Layout (bytes)
┌────────┬──────────┐
│ Địa chỉ│ Giá trị  │
├────────┼──────────┤
│   0    │          │
│   1    │          │
│   2    │          │
│   3    │          │
│   4    │          │
│   5    │          │
│   6    │          │
│   7    │          │
│  ...   │   ...    │
└────────┴──────────┘

1.2. Biến và Vùng Nhớ¤

Khi khai báo biến: - Máy tính dành riêng một vùng nhớ để lưu biến đó - Kích thước vùng nhớ phụ thuộc vào kiểu dữ liệu

Khi gọi tên biến: 1. Tìm kiếm địa chỉ ô nhớ của biến 2. Truy xuất hoặc thiết lập giá trị tại địa chỉ đó

Ví dụ:

C++
int main() {
    char ch = 'x';
    int a = 7;
}
Text Only
Memory Layout
┌────────┬──────────┬──────────┐
│ Địa chỉ│ Biến     │ Giá trị  │
├────────┼──────────┼──────────┤
│  ...   │          │          │
│ 0x10   │  ch      │   'x'    │
│ 0x11   │          │          │
│ 0x12   │          │          │
│ 0x13   │          │          │
│ 0x14   │  a       │    7     │
│ 0x15   │  a       │          │
│ 0x16   │  a       │          │
│ 0x17   │  a       │          │
│  ...   │          │          │
└────────┴──────────┴──────────┘

Lưu ý

  • char chiếm 1 byte (0x10)
  • int chiếm 4 bytes (0x14-0x17)

1.3. Toán Tử & và *¤

Toán tử & (Address-of Operator):

Đặt trước tên biến và cho biết địa chỉ của vùng nhớ của biến.

C++
&<tên_biến>

Toán tử * (Dereferencing/Indirection Operator):

Đặt trước một địa chỉ và cho biết giá trị lưu trữ tại địa chỉ đó.

C++
*(<địa_chỉ>)

Ví dụ:

C++
int value = 3200;
Text Only
┌────────┬──────────┬──────────┐
│ Địa chỉ│ Biến     │ Giá trị  │
├────────┼──────────┼──────────┤
│ 0x50   │  value   │   3200   │
└────────┴──────────┴──────────┘
C++
cout << "value = " << value;
// Output: value = 3200

cout << "&value = " << &value;
// Output: &value = 0x50

cout << "*(&value) = " << *(&value);
// Output: *(&value) = 3200

2. Khái Niệm Con Trỏ¤

2.1. Định Nghĩa¤

Con trỏ (Pointer):

Là một biến lưu trữ địa chỉ của một địa chỉ bộ nhớ. Địa chỉ này thường là địa chỉ của một biến khác.

graph LR
    A[Biến con trỏ x] -->|chứa địa chỉ| B[Biến y]
    A -.->|"ta nói x 'trỏ tới' y"| B
Hold "Alt" / "Option" to enable pan & zoom

Ví dụ minh họa:

Text Only
┌────────┬──────────┬──────────┐
│ Địa chỉ│ Biến     │ Giá trị  │
├────────┼──────────┼──────────┤
│ 0x34   │    y     │   100    │
│  ...   │          │          │
│ 0x90   │    x     │   0x34   │ ← x trỏ tới y
└────────┴──────────┴──────────┘

2.2. Phân Loại Con Trỏ¤

Con trỏ được phân loại theo kiểu dữ liệu mà nó trỏ tới:

  • Con trỏ kiểu int: Trỏ tới biến kiểu int
  • Con trỏ kiểu float: Trỏ tới biến kiểu float
  • Con trỏ kiểu char: Trỏ tới biến kiểu char
  • Con trỏ kiểu double: Trỏ tới biến kiểu double

3. Khai Báo Con Trỏ¤

3.1. Cú Pháp Khai Báo¤

C++
<kiểu_dữ_liệu> *<tên_biến_con_trỏ>;

Ví dụ:

C++
char char1;
int *ptrI;
float *ptrF;
Text Only
┌────────┬──────────┬──────────┐
│ Địa chỉ│ Biến     │ Giá trị  │
├────────┼──────────┼──────────┤
│ 0x10   │  ptrF    │    ?     │
│  ...   │          │          │
│ 0x50   │  char1   │    ?     │
│  ...   │          │          │
│ 0x80   │  ptrI    │    ?     │
└────────┴──────────┴──────────┘

Lưu ý

Con trỏ chưa khởi tạo có giá trị không xác định!

3.2. Khởi Tạo Con Trỏ¤

Cú pháp:

C++
<kiểu_dữ_liệu> *<tên_con_trỏ> = &<tên_biến>;

Ví dụ đúng:

C++
int a;
int *ptr = &a;  // ĐÚNG: Con trỏ int trỏ tới biến int
Text Only
┌────────┬──────────┬──────────┐
│ Địa chỉ│ Biến     │ Giá trị  │
├────────┼──────────┼──────────┤
│ 0x34   │    a     │    ?     │
│  ...   │          │          │
│ 0x90   │   ptr    │   0x34   │
└────────┴──────────┴──────────┘

Ví dụ sai:

C++
double a;
int *ptr = &a;  // SAI: Không khớp kiểu!

Quy tắc quan trọng

Kiểu của con trỏ phải khớp với kiểu của biến mà nó trỏ tới!

4. Con Trỏ và Toán Tử & , *¤

4.1. Sử Dụng Toán Tử *¤

Truy xuất giá trị:

C++
int a = 1000;
int *ptr = &a;

cout << ptr;     // 0x34 (địa chỉ của a)
cout << *ptr;    // 1000 (giá trị của a)

Thay đổi giá trị:

C++
*ptr = 3200;     // Thay đổi giá trị của a
cout << a;       // 3200
cout << *ptr;    // 3200

(*ptr)++;        // Tăng giá trị của a lên 1
cout << a;       // 3201
Text Only
Trước:                    Sau:
┌────────┬──────┐         ┌────────┬──────┐
│ 0x34   │ 1000 │ a       │ 0x34   │ 3201 │ a
├────────┼──────┤         ├────────┼──────┤
│ 0x90   │ 0x34 │ ptr     │ 0x90   │ 0x34 │ ptr
└────────┴──────┘         └────────┴──────┘

4.2. Ví Dụ Truy Vết¤

C++
#include <iostream>
using namespace std;

int main() {
    int a;
    int *ptr;
    int value;

    a = 3200;
    ptr = &a;
    value = --(*ptr);
}

Truy vết:

Text Only
Bước 1: a = 3200
┌────────┬──────┐
│ 0x34   │ 3200 │ a
├────────┼──────┤
│ 0x90   │  ?   │ ptr
├────────┼──────┤
│ 0x50   │  ?   │ value
└────────┴──────┘

Bước 2: ptr = &a
┌────────┬──────┐
│ 0x34   │ 3200 │ a
├────────┼──────┤
│ 0x90   │ 0x34 │ ptr
├────────┼──────┤
│ 0x50   │  ?   │ value
└────────┴──────┘

Bước 3: value = --(*ptr)
┌────────┬──────┐
│ 0x34   │ 3199 │ a  ← Giảm 1
├────────┼──────┤
│ 0x90   │ 0x34 │ ptr
├────────┼──────┤
│ 0x50   │ 3199 │ value ← Gán giá trị mới
└────────┴──────┘

Các giá trị:

C++
value     = 3199
ptr       = 0x34
a         = 3199
&value    = 0x50
&ptr      = 0x90
&a        = 0x34
*ptr      = 3199
&(*ptr)   = 0x34
*(*ptr)   = ERROR! (3199 không phải địa chỉ)
*(&(*ptr))= 3199

5. Phép Gán Con Trỏ¤

5.1. Gán Con Trỏ Cho Con Trỏ¤

C++
int *p1, *p2;
p2 = p1;  // p2 trỏ tới nơi mà p1 đang trỏ

Ví dụ:

C++
int x = 27, y = 5;
int *p1 = &x;
int *p2 = &y;

p1 = p2;  // p1 bây giờ trỏ tới y
Text Only
Trước:                    Sau:
┌────────┬──────┐         ┌────────┬──────┐
│ addr_x │  27  │ x       │ addr_x │  27  │ x
├────────┼──────┤         ├────────┼──────┤
│ addr_y │   5  │ y       │ addr_y │   5  │ y
├────────┼──────┤         ├────────┼──────┤
│        │addr_x│ p1      │        │addr_y│ p1 ← Thay đổi
├────────┼──────┤         ├────────┼──────┤
│        │addr_y│ p2      │        │addr_y│ p2
└────────┴──────┘         └────────┴──────┘

5.2. Gán Giá Trị Qua Con Trỏ¤

C++
*p2 = *p1;  // Gán giá trị trỏ bởi p1 cho giá trị trỏ bởi p2

Ví dụ:

C++
int x = 27, y = 5;
int *p1 = &x;
int *p2 = &y;

*p1 = *p2;  // x = y = 5
Text Only
Trước:                    Sau:
┌────────┬──────┐         ┌────────┬──────┐
│ addr_x │  27  │ x       │ addr_x │   5  │ x ← Thay đổi
├────────┼──────┤         ├────────┼──────┤
│ addr_y │   5  │ y       │ addr_y │   5  │ y
├────────┼──────┤         ├────────┼──────┤
│        │addr_x│ p1      │        │addr_x│ p1
├────────┼──────┤         ├────────┼──────┤
│        │addr_y│ p2      │        │addr_y│ p2
└────────┴──────┘         └────────┴──────┘

Phân biệt

  • p1 = p2: Gán con trỏ (thay đổi địa chỉ)
  • *p1 = *p2: Gán giá trị (thay đổi nội dung)

6. Con Trỏ NULL¤

6.1. Khái Niệm¤

Con trỏ NULL:

Con trỏ không trỏ vào đâu cả.

C++
int n;
int *p1 = &n;      // p1 trỏ tới n
int *p2;           // p2 chưa khởi tạo (nguy hiểm!)
int *p3 = NULL;    // p3 không trỏ vào đâu (an toàn)

Sử dụng:

C++
int *ptr = NULL;

if (ptr == NULL) {
    cout << "Con tro chua tro toi bien nao";
}

Khuyến nghị

  • Luôn khởi tạo con trỏ = NULL nếu chưa sử dụng
  • Kiểm tra con trỏ != NULL trước khi dùng

6.2. Con Trỏ Chưa Khởi Tạo vs NULL¤

C++
int *p1;        // unreferenced local variable (nguy hiểm)
int *p2 = NULL; // an toàn, biết rằng chưa trỏ vào đâu

7. Kích Thước Con Trỏ¤

Toán tử sizeof:

C++
sizeof(<kiểu_dữ_liệu>)
sizeof(<tên_biến>)

Ví dụ:

C++
int a;
double b;
char c;
int *pa;
double *pb;
char *pc;

// Kích thước biến
sizeof(a)       = 4   sizeof(int)     = 4
sizeof(b)       = 8   sizeof(double)  = 8
sizeof(c)       = 1   sizeof(char)    = 1

// Kích thước con trỏ
sizeof(pa)      = 4   sizeof(int*)    = 4
sizeof(pb)      = 4   sizeof(double*) = 4
sizeof(pc)      = 4   sizeof(char*)   = 4

Quan trọng

Mọi con trỏ đều có cùng kích thước (4 bytes trên hệ 32-bit, 8 bytes trên 64-bit) vì chúng chỉ lưu địa chỉ!

8. Từ Khóa const và Con Trỏ¤

8.1. Con Trỏ Thường¤

C++
int x;
int *p1 = &x;  // Non-const pointer to non-const int
  • Có thể thay đổi địa chỉ: p1 = &y;
  • Có thể thay đổi giá trị: *p1 = 10;

8.2. Con Trỏ Tới Hằng¤

C++
const int *p2 = &x;  // Non-const pointer to const int
  • Có thể thay đổi địa chỉ: p2 = &y;
  • KHÔNG thể thay đổi giá trị: *p2 = 10;

8.3. Con Trỏ Hằng¤

C++
int * const p3 = &x;  // Const pointer to non-const int
  • KHÔNG thể thay đổi địa chỉ: p3 = &y;
  • Có thể thay đổi giá trị: *p3 = 10;

8.4. Con Trỏ Hằng Tới Hằng¤

C++
const int * const p4 = &x;  // Const pointer to const int
  • KHÔNG thể thay đổi địa chỉ: p4 = &y;
  • KHÔNG thể thay đổi giá trị: *p4 = 10;

Bảng tổng hợp:

Loại Thay đổi địa chỉ Thay đổi giá trị
int *p
const int *p
int * const p
const int * const p

9. Con Trỏ và Hàm¤

9.1. Truyền Tham Số Bằng Con Trỏ¤

Cách 1: Truyền giá trị (không thay đổi)

C++
int NhapGiaTri() {
    int b;
    cout << "Nhap gia tri vao: ";
    cin >> b;
    return b;
}

int main() {
    int a;
    a = NhapGiaTri();
    cout << a;
}

Cách 2: Truyền con trỏ (có thể thay đổi)

C++
void NhapGiaTri(int *b) {
    cout << "Nhap gia tri vao: ";
    cin >> *b;
}

int main() {
    int a;
    NhapGiaTri(&a);
    cout << a;
}

Cách 3: Truyền tham chiếu

C++
void NhapGiaTri(int &b) {
    cout << "Nhap gia tri vao: ";
    cin >> b;
}

int main() {
    int a;
    NhapGiaTri(a);
    cout << a;
}

9.2. Hàm Hoán Vị¤

Sử dụng con trỏ:

C++
void Swap(int *a, int *b) {
    int temp = *b;
    *b = *a;
    *a = temp;
}

int main() {
    int x = 7, y = 8;
    Swap(&x, &y);
    cout << "x = " << x << ", y = " << y;
    // Output: x = 8, y = 7
}

Sử dụng tham chiếu:

C++
void Swap(int &a, int &b) {
    int temp = b;
    b = a;
    a = temp;
}

int main() {
    int x = 7, y = 8;
    Swap(x, y);
    cout << "x = " << x << ", y = " << y;
    // Output: x = 8, y = 7
}

10. Một Số Lưu ݤ

10.1. Quy Tắc Quan Trọng¤

Với int a, *pa = &a;

C++
*pa    a       // Cùng chỉ nội dung của biến a
pa     &a      // Cùng chỉ địa chỉ của biến a

10.2. Lỗi Thường Gặp¤

Lỗi 1: Sử dụng con trỏ chưa khởi tạo

C++
int *pa;
*pa = 1904;  // SAI! pa chưa trỏ vào đâu

Đúng:

C++
int a;
int *pa = &a;
*pa = 1904;  // ĐÚNG

Lỗi 2: Không khớp kiểu

C++
double a;
int *ptr = &a;  // SAI! Không khớp kiểu

10.3. Khuyến Nghị¤

  1. Không dùng con trỏ khi chưa khởi tạo
  2. Khởi tạo con trỏ = NULL nếu chưa dùng
  3. Kiểm tra con trỏ != NULL trước khi dùng
  4. Đảm bảo kiểu con trỏ khớp với kiểu biến

11. Bài Tập¤

Bài Tập 1: Tìm Lỗi¤

C++
int main() {
    int x, *p;
    x = 10;
    *p = x;  // LỖI: p chưa khởi tạo!
    return 0;
}

Sửa:

C++
int main() {
    int x, *p;
    x = 10;
    p = &x;  // Khởi tạo p trước
    *p = 20; // Thay đổi giá trị x
    return 0;
}

Bài Tập 2: Truy Vết¤

C++
#include <iostream>
using namespace std;

int main() {
    int i = 12;
    int *p1;
    p1 = &i;
    *p1 = 24;
    cout << *p1 << " " << i << endl;
}

Output: 24 24

Bài Tập 3: Hoán Vị Hai Số¤

Đề bài: Viết hàm hoán đổi giá trị của 2 tham số

Giải:

C++
// Cách 1: Con trỏ
void Swap(int *a, int *b) {
    int temp = *b;
    *b = *a;
    *a = temp;
}

int main() {
    int x = 7, y = 8;
    Swap(&x, &y);
    cout << "x = " << x << ", y = " << y;
}

// Cách 2: Tham chiếu
void Swap(int &a, int &b) {
    int temp = b;
    b = a;
    a = temp;
}

int main() {
    int x = 7, y = 8;
    Swap(x, y);
    cout << "x = " << x << ", y = " << y;
}

Tổng kết Con Trỏ Cơ Bản

Khái niệm:

  • Con trỏ lưu địa chỉ của biến
  • "Trỏ tới" nghĩa là chứa địa chỉ

Toán tử:

  • &: Lấy địa chỉ
  • *: Lấy giá trị tại địa chỉ

Khai báo:

  • int *p; - Khai báo con trỏ
  • p = &a; - Khởi tạo con trỏ
  • *p = 10; - Thay đổi giá trị

Con trỏ NULL:

  • Không trỏ vào đâu
  • Luôn khởi tạo = NULL nếu chưa dùng

const và con trỏ:

  • const int *p: Con trỏ tới hằng
  • int * const p: Con trỏ hằng

Truyền cho hàm:

  • Cho phép hàm thay đổi biến gốc
  • Tiết kiệm bộ nhớ với dữ liệu lớn

Chương tiếp theo: Con trỏ và Mảng