Bài 12.1: Bài tập thực hành duyệt mảng 1 chiều trong C++

Chào mừng trở lại với loạt bài học lập trình C++ cùng FullhouseDev! Sau khi đã làm quen với khái niệm mảng 1 chiều, giờ là lúc chúng ta đặt tay vào thực hành một trong những thao tác cốt lõiquan trọng nhất khi làm việc với mảng: duyệt mảng.

Duyệt mảng (Array Traversal) đơn giản là quá trình truy cập hoặc xử lý từng phần tử một trong mảng. Tại sao lại cần duyệt? Vì mảng lưu trữ nhiều dữ liệu cùng loại, và để làm bất cứ điều gì với dữ liệu đó (như in ra màn hình, tính tổng, tìm kiếm, thay đổi giá trị...), chúng ta cần phải có cách để "ghé thăm" từng "ngôi nhà" (phần tử) trong "khu phố" (mảng).

Trong C++, công cụ thông dụngmạnh mẽ nhất để thực hiện việc này chính là vòng lặp for.

Duyệt Mảng Bằng Vòng Lặp for Truyền Thống

Vòng lặp for truyền thống có cấu trúc for (khởi tạo; điều kiện; cập nhật). Khi áp dụng cho mảng 1 chiều, chúng ta thường sử dụng một biến chỉ mục (index), thường là i, bắt đầu từ 0 và tăng dần cho đến khi nó lớn hơn hoặc bằng kích thước của mảng.

Nhớ rằng, chỉ mục của mảng trong C++ bắt đầu từ 0. Nếu mảng có N phần tử, các chỉ mục hợp lệ sẽ là từ 0 đến N-1.

Cấu trúc chung sẽ là:

for (int i = 0; i < kichThuocMang; ++i) {
    // Thực hiện thao tác với phần tử mang[i] tại đây
}

Trong đó:

  • int i = 0;: Khởi tạo chỉ mục bắt đầu từ 0.
  • i < kichThuocMang;: Điều kiện để vòng lặp tiếp tục. Vòng lặp chạy khi i còn nhỏ hơn kichThuocMang. Điều này đảm bảo chúng ta truy cập đến phần tử cuối cùng có chỉ mục là kichThuocMang - 1.
  • ++i;: Cập nhật chỉ mục, tăng i lên 1 sau mỗi lần lặp để chuyển sang phần tử kế tiếp.
  • mang[i]: Cách truy cập giá trị của phần tử tại chỉ mục i.

Giờ, hãy cùng đi vào một số bài tập thực hành cụ thể để thấy rõ cách áp dụng.

Bài Tập 1: In Tất Cả Các Phần Tử Của Mảng

Đây là bài tập cơ bản nhất, giúp bạn làm quen với việc truy cập từng phần tử.

#include <iostream>
using namespace std;

int main() {
    int a[] = {10, 20, 30, 40, 50};
    int n = sizeof(a) / sizeof(a[0]);

    cout << "Cac phan tu cua mang la:" << endl;

    for (int i = 0; i < n; ++i) {
        cout << a[i] << " ";
    }
    cout << endl;

    return 0;
}

Output:

Cac phan tu cua mang la:
10 20 30 40 50
  • Giải thích:
    • Chúng ta khai báo một mảng mangSoNguyen và tính kichThuoc của nó.
    • Vòng lặp for bắt đầu với i = 0.
    • Trong mỗi lần lặp, chúng ta sử dụng mangSoNguyen[i] để truy cập giá trị của phần tử tại chỉ mục hiện tại i và in nó ra màn hình, theo sau là một dấu cách.
    • Vòng lặp tiếp tục cho đến khi i đạt đến kichThuoc (nghĩa là sau khi xử lý chỉ mục cuối cùng là kichThuoc - 1).
    • Kết quả in ra sẽ là: 10 20 30 40 50.
Bài Tập 2: Tính Tổng và Trung Bình Cộng Các Phần Tử

Một ứng dụng thực tế khác là tổng hợp dữ liệu từ mảng.

#include <iostream>
using namespace std;

int main() {
    int a[] = {10, 20, 30, 40, 50};
    int n = sizeof(a) / sizeof(a[0]);

    long long t = 0;
    for (int i = 0; i < n; ++i) {
        t += a[i];
    }

    cout << "Tong cac phan tu: " << t << endl;

    if (n > 0) {
        double tb = static_cast<double>(t) / n;
        cout << "Trung binh cac phan tu: " << tb << endl;
    } else {
        cout << "Mang rong, khong the tinh trung binh cong." << endl;
    }

    return 0;
}

Output:

Tong cac phan tu: 150
Trung binh cac phan tu: 30
  • Giải thích:
    • Chúng ta khai báo một biến tong và khởi tạo nó bằng 0. Biến này sẽ dùng để lưu tổng các phần tử.
    • Vòng lặp for duyệt qua mảng như bình thường.
    • Trong mỗi lần lặp, chúng ta lấy giá trị mangSoNguyen[i] và cộng nó vào biến tong bằng toán tử +=.
    • Sau khi vòng lặp kết thúc, biến tong sẽ chứa tổng của tất cả các phần tử.
    • Để tính trung bình, chúng ta chia tong cho kichThuoc. Quan trọng là phải ép kiểu (static_cast<double>) cho tong hoặc kichThuoc trước khi chia để đảm bảo kết quả là số thực (ví dụ: 50 / 5 = 10, nhưng 55 / 5 = 11, 55 / 6 cần số thực).
    • Chúng ta thêm một kiểm tra if (kichThuoc > 0) để tránh chia cho 0 nếu mảng rỗng.
Bài Tập 3: Tìm Giá Trị Lớn Nhất và Nhỏ Nhất

Bài tập này yêu cầu so sánh các phần tử khi duyệt qua mảng.

#include <iostream>
using namespace std;

int main() {
    int a[] = {10, 5, 30, 15, 25};
    int n = sizeof(a) / sizeof(a[0]);

    if (n == 0) {
        cout << "Mang rong, khong co gia tri max/min." << endl;
        return 1;
    }

    int max_val = a[0];
    int min_val = a[0];

    for (int i = 1; i < n; ++i) {
        if (a[i] > max_val) {
            max_val = a[i];
        }
        if (a[i] < min_val) {
            min_val = a[i];
        }
    }

    cout << "Gia tri lon nhat trong mang: " << max_val << endl;
    cout << "Gia tri nho nhat trong mang: " << min_val << endl;

    return 0;
}

Output:

Gia tri lon nhat trong mang: 30
Gia tri nho nhat trong mang: 5
  • Giải thích:
    • Đầu tiên, chúng ta kiểm tra xem mảng có rỗng không. Nếu có, không thể tìm max/min.
    • Chúng ta khởi tạo giaTriMaxgiaTriMin bằng giá trị của phần tử đầu tiên trong mảng (mangSoNguyen[0]). Đây là một cách khởi tạo an toàn nếu mảng không rỗng.
    • Vòng lặp for bắt đầu từ chỉ mục i = 1 (phần tử thứ hai), vì phần tử đầu tiên đã được dùng để khởi tạo.
    • Trong mỗi lần lặp, chúng ta so sánh mangSoNguyen[i] với giaTriMax hiện tại. Nếu mangSoNguyen[i] lớn hơn, chúng ta cập nhật giaTriMax bằng giá trị đó.
    • Tương tự, chúng ta so sánh mangSoNguyen[i] với giaTriMin hiện tại. Nếu mangSoNguyen[i] nhỏ hơn, chúng ta cập nhật giaTriMin.
    • Sau khi duyệt hết mảng, giaTriMaxgiaTriMin sẽ chứa giá trị lớn nhất và nhỏ nhất tìm được.
Bài Tập 4: Tìm Kiếm Một Giá Trị Cụ Thể (Tìm Kiếm Tuyến Tính)

Tìm kiếm một phần tử có tồn tại trong mảng hay không, và ở vị trí nào, là một bài toán rất phổ biến.

#include <iostream>
using namespace std;

int main() {
    int a[] = {10, 5, 30, 15, 25};
    int n = sizeof(a) / sizeof(a[0]);

    int gt = 15;
    int vt = -1;

    for (int i = 0; i < n; ++i) {
        if (a[i] == gt) {
            vt = i;
            break;
        }
    }

    if (vt != -1) {
        cout << "Tim thay " << gt << " tai vi tri (chi muc): " << vt << endl;
    } else {
        cout << "Khong tim thay " << gt << " trong mang." << endl;
    }

    int gt2 = 100;
    int vt2 = -1;

     for (int i = 0; i < n; ++i) {
        if (a[i] == gt2) {
            vt2 = i;
            break;
        }
    }

    if (vt2 != -1) {
        cout << "Tim thay " << gt2 << " tai vi tri (chi muc): " << vt2 << endl;
    } else {
        cout << "Khong tim thay " << gt2 << " trong mang." << endl;
    }

    return 0;
}

Output:

Tim thay 15 tai vi tri (chi muc): 3
Khong tim thay 100 trong mang.
  • Giải thích:
    • Chúng ta định nghĩa giaTriCanTim là giá trị mà chúng ta muốn tìm trong mảng.
    • Biến viTriTimThay được khởi tạo với -1. Đây là một giá trị không hợp lệ cho chỉ mục mảng, dùng để báo hiệu rằng giá trị chưa được tìm thấy.
    • Vòng lặp for duyệt qua mảng từ đầu đến cuối.
    • Trong mỗi lần lặp, chúng ta so sánh mangSoNguyen[i] với giaTriCanTim.
    • Nếu chúng bằng nhau, nghĩa là chúng ta đã tìm thấy giá trị. Chúng ta lưu lại chỉ mục i vào viTriTimThay và sử dụng lệnh break; để thoát khỏi vòng lặp ngay lập tức, vì chúng ta chỉ cần tìm lần xuất hiện đầu tiên.
    • Sau khi vòng lặp kết thúc, chúng ta kiểm tra giá trị của viTriTimThay. Nếu nó vẫn là -1, nghĩa là vòng lặp đã kết thúc mà không tìm thấy giá trị nào bằng giaTriCanTim. Ngược lại, viTriTimThay sẽ chứa chỉ mục của phần tử đầu tiên tìm được.
Duyệt Mảng Bằng Range-based for Loop (Từ C++11)

C++11 giới thiệu một cú pháp for mới, gọn gàng hơn khi bạn chỉ cần truy cập giá trị của từng phần tử mà không cần quan tâm đến chỉ mục của chúng.

Cú pháp: for (kieu_du_lieu ten_bien : ten_mang)

#include <iostream>
using namespace std;

int main() {
    int a[] = {10, 20, 30, 40, 50};

    cout << "Duyet mang bang range-based for loop (chi doc):" << endl;
    for (int x : a) {
        cout << x << " ";
    }
    cout << endl;

    cout << "Duyet mang bang range-based for loop (co sua doi):" << endl;
    int b[] = {1, 2, 3, 4, 5};
    for (int& x : b) {
        x = x * 2;
    }

    cout << "Mang sau khi nhan doi gia tri:" << endl;
     for (int x : b) {
        cout << x << " ";
    }
    cout << endl;

    return 0;
}

Output:

Duyet mang bang range-based for loop (chi doc):
10 20 30 40 50 
Duyet mang bang range-based for loop (co sua doi):
Mang sau khi nhan doi gia tri:
2 4 6 8 10
  • Giải thích:
    • Vòng lặp for (int phanTu : mangSoNguyen) sẽ tự động lặp qua từng phần tử của mangSoNguyen. Trong mỗi lần lặp, giá trị của phần tử hiện tại được gán vào biến phanTu. Tuy nhiên, phanTu ở đây là một bản sao của giá trị gốc trong mảng. Nếu bạn thay đổi phanTu, giá trị trong mảng không bị ảnh hưởng.
    • Để thay đổi giá trị của các phần tử ngay trong mảng khi sử dụng range-based for, bạn cần sử dụng tham chiếu (&). Ví dụ: for (int& phanTu : mangDeSua). Khi đó, phanTu không còn là bản sao nữa mà là một bí danh (alias) cho phần tử gốc trong mảng, và mọi thay đổi lên phanTu sẽ ảnh hưởng trực tiếp đến phần tử trong mảng.
    • Range-based for loop rất tiện lợi khi bạn chỉ cần duyệt qua tất cả các phần tử mà không cần quan tâm đến chỉ mục của chúng.
Một Vài Lưu Ý Quan Trọng
  • Kích Thước Mảng: Đối với mảng C++ kiểu C (như int arr[] = ...), việc lấy kích thước bằng sizeof(arr) / sizeof(arr[0]) chỉ hoạt động trong hàm nơi mảng được định nghĩa. Nếu bạn truyền mảng này vào một hàm khác, nó sẽ suy biến thành con trỏ, và sizeof sẽ cho kích thước của con trỏ chứ không phải mảng. Đây là lý do tại sao vector được ưa chuộng hơn trong C++ hiện đại, vì nó luôn "biết" kích thước của mình (vector.size()).
  • Truy Cập Ngoài Phạm Vi: C++ (với mảng kiểu C) không tự động kiểm tra xem bạn có đang truy cập mảng bằng một chỉ mục không hợp lệ (nhỏ hơn 0 hoặc lớn hơn hoặc bằng kích thước mảng) hay không. Truy cập ngoài phạm vi mảng là một lỗi nghiêm trọng có thể dẫn đến hành vi không xác định (Undefined Behavior), chương trình bị crash hoặc hoạt động sai. Luôn đảm bảo điều kiện vòng lặp của bạn là chính xác (i < kichThuocMang).

Comments

There are no comments at the moment.