Skip to content

L10. Con Trỏ và Mảng Một Chiều¤

1. Mảng Một Chiều và Địa Chỉ¤

1.1. Lấy Địa Chỉ Phần Tử Mảng¤

Cho mảng:

C++
int arr[6] = {5, 6, 9, 4, 1, 2};

Bộ nhớ:

Text Only
┌────────┬─────────┬──────────┐
│ Địa chỉ│ Phần tử │ Giá trị  │
├────────┼─────────┼──────────┤
│ 0x10   │ arr[0]  │    5     │
│ 0x14   │ arr[1]  │    6     │
│ 0x18   │ arr[2]  │    9     │
│ 0x22   │ arr[3]  │    4     │
│ 0x26   │ arr[4]  │    1     │
│ 0x30   │ arr[5]  │    2     │
└────────┴─────────┴──────────┘

Lấy địa chỉ:

C++
&arr[0]  // 0x10
&arr[1]  // 0x14
&arr[2]  // 0x18
&arr[3]  // 0x22
&arr[4]  // 0x26
&arr[5]  // 0x30

1.2. Mảng và Hằng Con Trỏ¤

Tên mảng là một hằng con trỏ:

C++
arr == &arr[0]  // ĐÚNG: arr là địa chỉ đầu tiên
Text Only
┌────────┬─────────┬──────────┐
│ 0x10   │ arr[0]  │    5     │
│ 0x14   │ arr[1]  │    6     │
│ 0x18   │ arr[2]  │    9     │
│ 0x22   │ arr[3]  │    4     │
│ 0x26   │ arr[4]  │    1     │
│ 0x30   │ arr[5]  │    2     │
└────────┴─────────┴──────────┘
 arr = 0x10

Lưu ý

Tên mảng là hằng con trỏ, không thể thay đổi:

C++
int a[10];
a = a + 1;  // SAI! Không thể thay đổi

2. Phép Toán Số Học Trên Con Trỏ¤

2.1. Phép Cộng¤

Quy tắc:

Text Only
<con_trỏ> + n  →  <con_trỏ> + n × sizeof(<kiểu_dữ_liệu>)

Ví dụ:

C++
int arr[6] = {5, 6, 9, 4, 1, 2};
int *p = &arr[2];  // p trỏ tới arr[2] (địa chỉ 0x18)
Text Only
┌────────┬─────────┬──────────┐
│ 0x10   │ arr[0]  │    5     │
│ 0x14   │ arr[1]  │    6     │
│ 0x18   │ arr[2]  │    9     │ ← p
│ 0x22   │ arr[3]  │    4     │ ← p+1
│ 0x26   │ arr[4]  │    1     │ ← p+2
│ 0x30   │ arr[5]  │    2     │
└────────┴─────────┴──────────┘
C++
p       // 0x18
p + 1   // 0x18 + 1×4 = 0x22
p + 2   // 0x18 + 2×4 = 0x26

*(p + 1)  // 4 (giá trị tại 0x22)
*(p + 2)  // 1 (giá trị tại 0x26)

Toán tử gộp:

C++
p += 1;   // p = p + 1
p++;      // p = p + 1

2.2. Phép Trừ¤

Quy tắc:

Text Only
<con_trỏ> - n  →  <con_trỏ> - n × sizeof(<kiểu_dữ_liệu>)

Ví dụ:

C++
int arr[6] = {5, 6, 9, 4, 1, 2};
int *p = &arr[4];  // p trỏ tới arr[4] (địa chỉ 0x26)
Text Only
┌────────┬─────────┬──────────┐
│ 0x10   │ arr[0]  │    5     │
│ 0x14   │ arr[1]  │    6     │
│ 0x18   │ arr[2]  │    9     │ ← p-2
│ 0x22   │ arr[3]  │    4     │ ← p-1
│ 0x26   │ arr[4]  │    1     │ ← p
│ 0x30   │ arr[5]  │    2     │
└────────┴─────────┴──────────┘
C++
p       // 0x26
p - 1   // 0x26 - 1×4 = 0x22
p - 2   // 0x26 - 2×4 = 0x18

2.3. Khoảng Cách Giữa Hai Con Trỏ¤

C++
<con_trỏ_1> - <con_trỏ_2>  // Kết quả: số phần tử

Ví dụ:

C++
int arr[6] = {5, 6, 9, 4, 1, 2};
int *p1 = &arr[1];  // 0x14
int *p2 = &arr[5];  // 0x30

p2 - p1  // (0x30 - 0x14) / 4 = 4 phần tử
p1 - p2  // -4

2.4. Phép So Sánh¤

C++
==, !=, >, >=, <, <=

So sánh địa chỉ (thứ tự ô nhớ)

C++
int arr[6] = {5, 6, 9, 4, 1, 2};
int *p1 = &arr[1];
int *p2 = &arr[5];

p1 < p2   // true (0x14 < 0x30)
p1 == p2  // false

Phép toán không hợp lệ

KHÔNG thể thực hiện: *, /, %

3. Con Trỏ và Mảng Một Chiều¤

3.1. Gán Con Trỏ Cho Mảng¤

C++
int arr[6] = {5, 6, 9, 4, 1, 2};
int *parr;

// Cách 1
parr = arr;

// Cách 2
parr = &arr[0];
Text Only
┌────────┬─────────┬──────────┐
│ 0x10   │ arr[0]  │    5     │
│ 0x14   │ arr[1]  │    6     │
│ 0x18   │ arr[2]  │    9     │
│ 0x22   │ arr[3]  │    4     │
│ 0x26   │ arr[4]  │    1     │
│ 0x30   │ arr[5]  │    2     │
├────────┼─────────┼──────────┤
│ 0x90   │  parr   │   0x10   │ ← Trỏ tới arr[0]
└────────┴─────────┴──────────┘

3.2. Truy Xuất Qua Con Trỏ¤

Với chỉ số i hợp lệ:

C++
arr[i]    *(arr + i)    parr[i]    *(parr + i)

Lấy địa chỉ:

C++
&arr[i]    arr + i    &parr[i]    parr + i

Ví dụ:

C++
int arr[6] = {5, 6, 9, 4, 1, 2};
int *parr = arr;

// Tất cả đều truy xuất arr[2]
arr[2]        // 9
*(arr + 2)    // 9
parr[2]       // 9
*(parr + 2)   // 9

// Tất cả đều lấy địa chỉ arr[2]
&arr[2]       // 0x18
arr + 2       // 0x18
&parr[2]      // 0x18
parr + 2      // 0x18

3.3. Bảng Tương Đương¤

Lấy giá trị Lấy địa chỉ
arr[i] &arr[i]
*(arr+i) arr+i
parr[i] &parr[i]
*(parr+i) parr+i

4. Truyền Mảng Cho Hàm¤

4.1. Đặc Điểm¤

Mảng truyền cho hàm không phải hằng con trỏ:

C++
int main() {
    int a[] = {1, 2, 3, 4, 5, 6};

    // SAI: a là hằng con trỏ
    for (int i = 0; i < 6; i++)
        printf("%d", *(a++));
}
C++
void xuat(int *a, int n) {
    // ĐÚNG: a là tham số, không phải hằng
    for (int i = 0; i < n; i++)
        printf("%d", *(a++));
}

int main() {
    int a[] = {1, 2, 3, 4, 5, 6};
    xuat(a, 6);
}

4.2. Các Cách Khai Báo Tham Số¤

C++
void xuat(int a[], int n);       // Cách 1
void xuat(int a[100], int n);    // Cách 2 (số không quan trọng)
void xuat(int *a, int n);        // Cách 3 (khuyến nghị)

Ví dụ đầy đủ:

C++
void nhap(int *a, int n) {
    for (int i = 0; i < n; i++) {
        cout << "Nhap a[" << i << "] = ";
        cin >> *(a + i);  // Hoặc cin >> a[i];
    }
}

void xuat(int *a, int n) {
    for (int i = 0; i < n; i++) {
        cout << *(a + i) << " ";  // Hoặc cout << a[i];
    }
}

int main() {
    int arr[100], n;
    cout << "Nhap n: "; cin >> n;
    nhap(arr, n);
    xuat(arr, n);
}

5. Bài Tập¤

Bài Tập 1: Nhóm Biểu Thức¤

Cho mảng int a[] và con trỏ int *p = a. Nhóm các biểu thức sau thành 2 nhóm:

Nhóm lấy giá trị: - a[i] - *(a + i) - p[i] - *(p + i)

Nhóm lấy địa chỉ: - &a[i] - a + i - &p[i] - p + i

Bài Tập 2: Gán Giá Trị Qua Con Trỏ¤

C++
int a[10];
int *p = a;

// Gán giá trị 100 cho phần tử thứ 5
*(p + 5) = 100;  // Cách 1
p[5] = 100;      // Cách 2
a[5] = 100;      // Cách 3

Bài Tập 3: Nhập/Xuất Mảng¤

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

const int n = 10;

int main() {
    int a[n], *p = a;

    // Nhập mảng
    for (int i = 0; i < n; i++) {
        cin >> *(p + i);
    }

    // Xuất mảng
    for (int i = 0; i < n; i++) {
        cout << *(p + i) << " ";
    }
}

Bài Tập 4: Chuyển Chuỗi Sang Chữ Hoa¤

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

int main() {
    char str[20] = "hello class";
    char *p = str;
    int n = strlen(str);

    for (int i = 0; i < n; i++)
        p[i] = toupper(p[i]);

    cout << p;  // Output: HELLO CLASS
}

6. Lưu Ý Quan Trọng¤

Các lưu ý

  1. Không thực hiện phép nhân, chia, lấy phần dư trên con trỏ
  2. Tăng/giảm con trỏ n đơn vị = tăng/giảm n × sizeof(kiểu_dữ_liệu) bytes
  3. Không thể tăng/giảm biến mảng (vì là hằng con trỏ)
  4. Khi truyền mảng cho hàm, tham số mảng không phải hằng con trỏ

Cấp Phát Động (Dynamic Memory Allocation)¤

1. Cấp Phát Bộ Nhớ Tĩnh vs Động¤

1.1. Cấp Phát Tĩnh¤

Đặc điểm: - Khai báo biến, mảng với kích thước cố định - Phải biết trước cần bao nhiêu bộ nhớ - Không thay đổi được kích thước - Tốn bộ nhớ nếu khai báo quá lớn

C++
int a[1000];  // Luôn tốn 4000 bytes, dù chỉ dùng 10 phần tử

1.2. Cấp Phát Động¤

Đặc điểm: - Cấp phát khi cần, giải phóng khi không dùng - Không cần biết trước kích thước - Có thể thay đổi kích thước - Sử dụng vùng nhớ HEAP (và bộ nhớ ảo)

C++
int n;
cin >> n;
int *a = new int[n];  // Cấp phát đúng n phần tử cần dùng

1.3. Cấu Trúc Chương Trình C++ Trong Bộ Nhớ¤

Text Only
┌─────────────────────────┐
│        STACK            │ ← Biến cục bộ, tham số hàm
│    (Last-In First-Out)  │   Tự động quản lý
├─────────────────────────┤
│                         │
│    Vùng nhớ trống      │
│                         │
├─────────────────────────┤
│        HEAP             │ ← Cấp phát động
│   (Dùng new/delete)     │   RAM + Bộ nhớ ảo
├─────────────────────────┤
│   Biến toàn cục/tĩnh   │ ← Kích thước cố định
├─────────────────────────┤
│    Mã chương trình      │ ← Lệnh và hằng số
└─────────────────────────┘

2. Toán Tử new¤

2.1. Cấp Phát Cho Biến Đơn¤

Cú pháp:

C++
<kiểu> *<tên_con_trỏ> = new <kiểu>;

Ví dụ:

C++
int *ptr = new int;  // Cấp phát 1 biến int
Text Only
┌────────┬──────────┐
│ 0x34   │    ?     │ ← Vùng nhớ được cấp phát
├────────┼──────────┤
│ 0x90   │   0x34   │ ptr
└────────┴──────────┘

Sử dụng:

C++
*ptr = 3199;
cout << *ptr;  // 3199

2.2. Khởi Tạo Giá Trị¤

C++
int *p = new int(99);  // Khởi tạo = 99
cout << *p;  // 99

2.3. Kiểm Tra Cấp Phát Thành Công¤

C++
int *p = new int;
if (p == NULL) {
    cout << "Error: Khong du bo nho.\n";
    exit(1);
}
*p = 3199;

3. Toán Tử delete¤

3.1. Giải Phóng Bộ Nhớ¤

Cú pháp:

C++
delete <tên_con_trỏ>;

Ví dụ:

C++
int *p = new int(100);
cout << *p;  // 100
delete p;    // Giải phóng bộ nhớ
p = NULL;    // Tránh con trỏ lạc

Con trỏ lạc (Dangling Pointer)

Sau khi delete, con trỏ vẫn trỏ tới vùng nhớ cũ nhưng vùng nhớ đã được giải phóng!

Giải pháp: Gán p = NULL sau khi delete

3.2. Ví Dụ Con Trỏ Lạc¤

C++
int *p1, *p2;
p1 = new int;
*p1 = 30;
p2 = p1;
*p2 = 40;
p1 = new int;
*p1 = 50;

Truy vết:

Text Only
Bước 1: p1 = new int
┌────┐ ┌────┐
│ p1 │ │ p2 │
└─┬──┘ └────┘
 [?]

Bước 2: *p1 = 30
┌────┐ ┌────┐
│ p1 │ │ p2 │
└─┬──┘ └────┘
 [30]

Bước 3: p2 = p1
┌────┐ ┌────┐
│ p1 │ │ p2 │
└─┬──┘ └─┬──┘
  │      │
  └──┬───┘
    [30]

Bước 4: *p2 = 40
┌────┐ ┌────┐
│ p1 │ │ p2 │
└─┬──┘ └─┬──┘
  │      │
  └──┬───┘
    [40]

Bước 5: p1 = new int
┌────┐ ┌────┐
│ p1 │ │ p2 │
└─┬──┘ └─┬──┘
  │      │
  ↓      ↓
 [?]    [40] ← Bị mất tham chiếu (memory leak)

Bước 6: *p1 = 50
┌────┐ ┌────┐
│ p1 │ │ p2 │
└─┬──┘ └─┬──┘
  │      │
  ↓      ↓
 [50]   [40]

4. Mảng Động Một Chiều¤

4.1. Cấp Phát Mảng Động¤

Cú pháp:

C++
<kiểu> *<tên_con_trỏ> = new <kiểu>[<số_phần_tử>];

Ví dụ:

C++
int n;
cout << "Nhap n: ";
cin >> n;

int *a = new int[n];  // Cấp phát mảng n phần tử

// Sử dụng như mảng thường
for (int i = 0; i < n; i++) {
    a[i] = i + 1;
}

4.2. Xóa Mảng Động¤

C++
delete[] <tên_con_trỏ>;

Ví dụ:

C++
int *a = new int[10];

// Sử dụng mảng
for (int i = 0; i < 10; i++) {
    a[i] = i;
}

// Giải phóng
delete[] a;  // Chú ý: delete[]
a = NULL;

Quan trọng

  • Cấp phát bằng new → Giải phóng bằng delete
  • Cấp phát bằng new[] → Giải phóng bằng delete[]

4.3. Hàm Trả Về Mảng Động¤

Cách 1: Trả về con trỏ

C++
int* NhapMang(int n) {
    int *p = new int[n];
    for (int i = 0; i < n; i++) {
        cin >> p[i];
    }
    return p;
}

int main() {
    int n;
    cin >> n;
    int *arr = NhapMang(n);

    // Sử dụng arr

    delete[] arr;  // Nhớ giải phóng
}

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

C++
void NhapMang(int*& p, int n) {
    p = new int[n];
    for (int i = 0; i < n; i++) {
        cin >> p[i];
    }
}

int main() {
    int *arr, n;
    cin >> n;
    NhapMang(arr, n);

    // Sử dụng arr

    delete[] arr;
}

5. Mảng Động Hai Chiều¤

5.1. Cấp Phát Ma Trận¤

C++
int m, n;
cin >> m >> n;

// Cấp phát mảng con trỏ
int **a = new int*[m];

// Cấp phát từng dòng
for (int i = 0; i < m; i++) {
    a[i] = new int[n];
}

Minh họa:

Text Only
a → [ptr0] → [a00][a01][a02]...[a0n]
    [ptr1] → [a10][a11][a12]...[a1n]
    [ptr2] → [a20][a21][a22]...[a2n]
    ...
    [ptrm] → [am0][am1][am2]...[amn]

5.2. Sử Dụng Ma Trận¤

C++
// Nhập
for (int i = 0; i < m; i++) {
    for (int j = 0; j < n; j++) {
        cin >> a[i][j];
    }
}

// Xuất
for (int i = 0; i < m; i++) {
    for (int j = 0; j < n; j++) {
        cout << a[i][j] << " ";
    }
    cout << endl;
}

5.3. Giải Phóng Ma Trận¤

C++
// Giải phóng từng dòng
for (int i = 0; i < m; i++) {
    delete[] a[i];
}

// Giải phóng mảng con trỏ
delete[] a;
a = NULL;

5.4. Chương Trình Hoàn Chỉnh¤

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

int main() {
    int m, n;
    cout << "Nhap so dong, so cot: ";
    cin >> m >> n;

    // Cấp phát
    int **a = new int*[m];
    for (int i = 0; i < m; i++) {
        a[i] = new int[n];
    }

    // Nhập
    cout << "Nhap ma tran:\n";
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            cin >> a[i][j];
        }
    }

    // Xuất
    cout << "Ma tran vua nhap:\n";
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            cout << a[i][j] << " ";
        }
        cout << endl;
    }

    // Giải phóng
    for (int i = 0; i < m; i++) {
        delete[] a[i];
    }
    delete[] a;

    return 0;
}

6. Typedef Với Con Trỏ¤

C++
typedef int* IntPtr;

IntPtr p;              // int *p;
IntPtr p1, p2;         // int *p1, *p2;

Ví dụ sử dụng:

C++
typedef int* IntPointer;

void Input(IntPointer temp) {
    *temp = 20;
    cout << "Trong ham: *temp = " << *temp << endl;
}

int main() {
    IntPointer p = new int;
    *p = 10;
    cout << "Truoc khi goi: *p = " << *p << endl;
    Input(p);
    cout << "Sau khi goi: *p = " << *p << endl;
    delete p;
}

Output:

Text Only
Truoc khi goi: *p = 10
Trong ham: *temp = 20
Sau khi goi: *p = 20

7. Bài Tập Bắt Buộc¤

Bài 1: Mảng động

Nhập một dãy số hữu tỉ tùy ý (dùng cấp phát động), xuất ra dãy gồm các số < 1, tính tổng và tích.

Bài 2: Sao chép mảng

Viết hàm nhập dãy số thực A (cấp phát động). Viết hàm sao chép A sang dãy B (cấp phát lại).

Bài 3: Làm lại bài tập mảng

Làm lại các bài tập về mảng 1 chiều và 2 chiều sử dụng cấp phát động.


Bây giờ đã đầy đủ hơn! Còn thiếu phần Tập tin (File I/O). Tôi tiếp tục bổ sung không?