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

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õi và quan 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ụng và mạ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 khii
còn nhỏ hơnkichThuocMang
. Đ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ăngi
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ụci
.
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ínhkichThuoc
của nó. - Vòng lặp
for
bắt đầu vớii = 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ạii
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 đếnkichThuoc
(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
.
- Chúng ta khai báo một mảng
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ếntong
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
chokichThuoc
. Quan trọng là phải ép kiểu (static_cast<double>
) chotong
hoặckichThuoc
trước khi chia để đảm bảo kết quả là số thực (ví dụ:50 / 5 = 10
, nhưng55 / 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.
- Chúng ta khai báo một biến
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
giaTriMax
vàgiaTriMin
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ụci = 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ớigiaTriMax
hiện tại. NếumangSoNguyen[i]
lớn hơn, chúng ta cập nhậtgiaTriMax
bằng giá trị đó. - Tương tự, chúng ta so sánh
mangSoNguyen[i]
vớigiaTriMin
hiện tại. NếumangSoNguyen[i]
nhỏ hơn, chúng ta cập nhậtgiaTriMin
. - Sau khi duyệt hết mảng,
giaTriMax
vàgiaTriMin
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ớigiaTriCanTim
. - 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àoviTriTimThay
và sử dụng lệnhbreak;
để 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ằnggiaTriCanTim
. Ngược lại,viTriTimThay
sẽ chứa chỉ mục của phần tử đầu tiên tìm được.
- Chúng ta định nghĩa
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ủamangSoNguyen
. Trong mỗi lần lặp, giá trị của phần tử hiện tại được gán vào biếnphanTu
. 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 đổiphanTu
, 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ênphanTu
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.
- Vòng lặp
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ằngsizeof(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 saovector
đượ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