Bài 11.2: Duyệt mảng 1 chiều bằng chỉ số trong C++

Bài 11.2: Duyệt mảng 1 chiều bằng chỉ số trong C++
Chào mừng trở lại với loạt bài viết về C++! Sau khi đã làm quen với khái niệm mảng một chiều, bước tiếp theo và vô cùng quan trọng là học cách truy cập và làm việc với từng phần tử bên trong mảng đó. Quá trình này được gọi là duyệt mảng (array traversal).
Trong C++, có nhiều cách để duyệt mảng, nhưng phương pháp sử dụng chỉ số (index) là cốt lõi, cơ bản và mang lại cho chúng ta sức mạnh kiểm soát tuyệt đối trên từng vị trí trong mảng. Hãy cùng khám phá phương pháp này nhé!
Chỉ số (Index) trong Mảng: Người bạn đồng hành
Trước hết, hãy nhắc lại một chút về chỉ số. Trong C++ (và hầu hết các ngôn ngữ lập trình khác), các phần tử trong mảng một chiều được đánh số thứ tự bắt đầu từ 0.
- Phần tử đầu tiên có chỉ số là 0.
- Phần tử thứ hai có chỉ số là 1.
- ...
- Phần tử cuối cùng của một mảng có
kích thước
làN
sẽ có chỉ số là N-1.
Khi duyệt mảng bằng chỉ số, chúng ta sẽ sử dụng một biến (thường là kiểu số nguyên) để đại diện cho chỉ số hiện tại mà chúng ta đang truy cập. Biến này sẽ thay đổi giá trị, đi từ chỉ số đầu tiên (0) đến chỉ số cuối cùng (N-1).
Vòng lặp for
- Công cụ đắc lực
Để tự động hóa việc thay đổi chỉ số và thực hiện một hành động nào đó với từng phần tử, chúng ta sử dụng các cấu trúc lặp. Vòng lặp for
là lựa chọn phổ biến và phù hợp nhất khi duyệt mảng bằng chỉ số, bởi vì chúng ta biết chính xác số lần lặp cần thiết (bằng với số lượng phần tử của mảng).
Cấu trúc chung của vòng lặp for
để duyệt mảng kích thước kich_thuoc
sẽ là:
for (int i = 0; i < kich_thuoc; ++i) {
// Làm gì đó với phan tu tai chi so 'i',
// truy cap bang: ten_mang[i]
}
int i = 0
: Khởi tạo biến chỉ sối
bắt đầu từ 0.i < kich_thuoc
: Điều kiện lặp. Vòng lặp tiếp tục chừng nàoi
còn nhỏ hơn kích thước của mảng. Điều này đảm bảoi
sẽ chạy từ0
đếnkich_thuoc - 1
, bao trọn tất cả các chỉ số hợp lệ.++i
: Tăng giá trị củai
sau mỗi lần lặp, để chuyển sang chỉ số kế tiếp.ten_mang[i]
: Đây là cách chúng ta truy cập giá trị của phần tử tại chỉ sối
hiện tại trong mảng.
Bây giờ, hãy cùng xem một vài ví dụ minh họa nhé!
Ví dụ 1: In tất cả các phần tử của mảng
Đây là tác vụ cơ bản nhất khi duyệt mảng: chỉ đơn giản là xem bên trong nó có gì.
#include <iostream>
#include <vector> // Sử dụng vector cho dễ quản lý kích thước
int main() {
// Khai báo và khởi tạo một mảng (sử dụng vector)
vector<int> diemSo = {85, 90, 78, 92, 88, 95};
// Lấy kích thước của mảng
// Đối với vector, sử dụng phương thức size()
int soLuongPhanTu = diemSo.size();
cout << "Danh sach diem so:" << endl;
// Duyệt mảng bằng chỉ số
for (int i = 0; i < soLuongPhanTu; ++i) {
// Truy cap va in gia tri tai chi so i
cout << "Diem tai chi so " << i << " la: " << diemSo[i] << endl;
}
return 0;
}
Giải thích:
- Chúng ta khai báo một
vector
tên làdiemSo
chứa các điểm số. Việc sử dụngvector
thay vì mảng C-style (int diemSo[] = ...;
) là thực hành tốt trong C++ hiện đại vì nó quản lý bộ nhớ linh hoạt hơn và cung cấp phương thức.size()
đáng tin cậy để lấy kích thước. soLuongPhanTu = diemSo.size();
lấy kích thước củavector
. Đối với một mảng C-styleint arr[10];
, kích thước là cố định 10. Nếu khai báoint arr[] = {1, 2, 3};
, kích thước sẽ là 3. Lưu ý rằng cách tính kích thước mảng C-style khi mảng được truyền vào hàm có thể phức tạp hơn (sizeof(arr)/sizeof(arr[0])
chỉ hoạt động khi mảng không bị phân rã thành con trỏ). Vớivector
,.size()
luôn chính xác.- Vòng lặp
for (int i = 0; i < soLuongPhanTu; ++i)
bắt đầu vớii=0
, chạy cho đến khii
bằngsoLuongPhanTu
(không bao gồmsoLuongPhanTu
), và tăngi
sau mỗi lần lặp. - Trong mỗi lần lặp,
diemSo[i]
truy cập đến phần tử tại chỉ sối
hiện tại. - Chúng ta in ra cả chỉ số (
i
) và giá trị (diemSo[i]
) để thấy rõ mối liên hệ.
Ví dụ 2: Tính tổng các phần tử trong mảng
Duyệt mảng không chỉ để in ra, mà còn để thực hiện tính toán trên các phần tử.
#include <iostream>
#include <vector>
#include <numeric> // Thư viện hữu ích cho các phép toán số học
int main() {
vector<double> doanhThuNgay = {150.5, 210.0, 185.75, 250.0, 199.99};
double tongDoanhThu = 0.0;
// Duyệt mảng để cộng dồn giá trị
for (int i = 0; i < doanhThuNgay.size(); ++i) {
tongDoanhThu += doanhThuNgay[i]; // Cong gia tri tai chi so i vao tong
}
cout << "Tong doanh thu trong " << doanhThuNgay.size() << " ngay la: " << tongDoanhThu << endl;
// Minh hoa su dung ham trong <numeric> - cach hien dai hon cho tong don gian
// double tongDoanhThuModern = accumulate(doanhThuNgay.begin(), doanhThuNgay.end(), 0.0);
// cout << "(Su dung accumulate) Tong doanh thu: " << tongDoanhThuModern << endl;
return 0;
}
Giải thích:
- Chúng ta sử dụng vòng lặp
for
để đi qua từng chỉ số từ 0 đến kích thước mảng trừ 1. - Biến
tongDoanhThu
được khởi tạo bằng 0 trước vòng lặp. - Bên trong vòng lặp,
tongDoanhThu += doanhThuNgay[i];
lấy giá trị của phần tử tại chỉ sối
(doanhThuNgay[i]
) và cộng nó vàotongDoanhThu
. - Sau khi vòng lặp kết thúc,
tongDoanhThu
chứa tổng của tất cả các phần tử trong mảng. - (Đoạn code bị chú thích) Minh họa rằng có những cách hiện đại hơn (
accumulate
) cho các tác vụ phổ biến như tính tổng, nhưng vòng lặp index vẫn là cơ sở để hiểu và thực hiện các thao tác phức tạp hơn.
Ví dụ 3: Tìm kiếm một phần tử và vị trí của nó
Nếu bạn cần biết một giá trị cụ thể có tồn tại trong mảng hay không, và nếu có thì nó nằm ở vị trí (chỉ số) nào, duyệt bằng chỉ số là cách tự nhiên để làm điều đó.
#include <iostream>
#include <vector>
#include <string>
int main() {
vector<string> danhSachHangHoa = {"Ao Thun", "Quan Bo", "Ao Khoac", "Quan Short", "Ao Thun"};
string hangHoaCanTim = "Ao Khoac";
int viTriTimThay = -1; // Sử dụng -1 để biểu thị không tìm thấy
cout << "Tim kiem '" << hangHoaCanTim << "' trong danh sach..." << endl;
// Duyệt mảng để tìm kiếm
for (int i = 0; i < danhSachHangHoa.size(); ++i) {
if (danhSachHangHoa[i] == hangHoaCanTim) {
viTriTimThay = i; // Lưu lại chỉ số khi tìm thấy
break; // Tim thay roi thi thoat vong lap luon (hieu qua hon)
}
}
// Kiem tra ket qua
if (viTriTimThay != -1) {
cout << "Tim thay '" << hangHoaCanTim << "' tai chi so: " << viTriTimThay << endl;
} else {
cout << "'" << hangHoaCanTim << "' khong co trong danh sach." << endl;
}
return 0;
}
Giải thích:
- Chúng ta khởi tạo
viTriTimThay
bằng -1. - Vòng lặp
for
đi qua từng chỉ sối
. - Trong mỗi lần lặp, câu lệnh
if (danhSachHangHoa[i] == hangHoaCanTim)
so sánh phần tử tại chỉ sối
với giá trị cần tìm. - Nếu tìm thấy, chúng ta cập nhật
viTriTimThay
bằng chỉ sối
hiện tại và sử dụngbreak;
để thoát khỏi vòng lặp ngay lập tức. Điều này giúp tối ưu hóa hiệu suất, vì không cần kiểm tra các phần tử còn lại nếu đã tìm thấy. - Sau vòng lặp, chúng ta kiểm tra giá trị của
viTriTimThay
để biết kết quả tìm kiếm.
Ví dụ 4: Duyệt mảng theo chiều ngược lại
Đôi khi, bạn cần xử lý các phần tử từ cuối mảng trở về đầu. Duyệt bằng chỉ số cho phép bạn dễ dàng làm điều này.
#include <iostream>
#include <vector>
int main() {
vector<char> kyTu = {'A', 'B', 'C', 'D', 'E'};
cout << "Duyet mang theo chieu dao nguoc:" << endl;
// Duyệt mảng từ chi so cuoi cung ve 0
for (int i = kyTu.size() - 1; i >= 0; --i) {
cout << kyTu[i] << " "; // In phan tu tai chi so i
}
cout << endl; // Xuong dong sau khi in xong
return 0;
}
Giải thích:
- Thay vì bắt đầu từ 0 và tăng dần, chúng ta bắt đầu vòng lặp từ chỉ số cuối cùng của mảng, là
kyTu.size() - 1
. - Điều kiện lặp là
i >= 0
. Vòng lặp tiếp tục chừng nàoi
còn lớn hơn hoặc bằng 0 (bao gồm cả chỉ số 0). - Bước nhảy là
--i
, giảm giá trị củai
sau mỗi lần lặp. - Kết quả là vòng lặp sẽ truy cập các phần tử theo thứ tự
E
,D
,C
,B
,A
.
Sức mạnh và lưu ý khi dùng chỉ số
Tại sao lại dùng chỉ số?
- Truy cập trực tiếp: Chỉ số cho phép bạn truy cập bất kỳ phần tử nào trong mảng ngay lập tức nếu bạn biết chỉ số của nó (
ten_mang[i]
). - Kiểm soát linh hoạt: Bạn có thể duyệt từ đầu đến cuối, từ cuối về đầu, hoặc thậm chí bỏ qua các phần tử (ví dụ: tăng
i
lên 2 mỗi lần lặp để chỉ xử lý các phần tử ở chỉ số chẵn). - Thao tác tại chỗ: Chỉ số là cần thiết khi bạn muốn thay đổi giá trị của một phần tử cụ thể trong mảng (ví dụ:
ten_mang[i] = gia_tri_moi;
).
Lưu ý quan trọng:
- Lỗi ngoài phạm vi (Out of Bounds Error): Đây là lỗi phổ biến và nguy hiểm nhất khi làm việc với chỉ số. Nếu bạn cố gắng truy cập
ten_mang[i]
mài
nhỏ hơn 0 hoặc lớn hơn hoặc bằng kích thước mảng (i >= kich_thuoc
), chương trình của bạn có thể gặp lỗi nghiêm trọng (ví dụ: crash) hoặc cho kết quả sai không lường trước. - Luôn đảm bảo điều kiện vòng lặp của bạn chính xác để chỉ số không bao giờ vượt ra ngoài phạm vi
[0, kich_thuoc - 1]
. - Với
vector
, bạn có thể sử dụng phương thứcat()
thay vì toán tử[]
. Ví dụ:diemSo.at(i)
. Ưu điểm củaat()
là nó kiểm tra phạm vi. Nếui
nằm ngoài phạm vi hợp lệ,at()
sẽ ném ra một ngoại lệ (out_of_range
), giúp bạn phát hiện lỗi sớm hơn trong quá trình phát triển, thay vì gặp hành vi không xác định với[]
. Tuy nhiên,at()
có thể chậm hơn một chút do chi phí kiểm tra.
Bài tập ví dụ: C++ Bài 7.A2: Số lượng số chẵn
Lập trình nhập vào một số nguyên dương \(N\) và dãy số nguyên \(A\) gồm \(N\) phần tử. Đếm và thông báo số lượng số chẵn có mặt trong dãy \(A\). .
INPUT FORMAT
Dòng đầu là số nguyên dương \(N\) \((1 \leq N \leq 10 ^6)\).
Dòng thứ hai là dãy số nguyên \(A\) \((1 \leq |A_i| \leq 10^9)\).
OUTPUT FORMAT
In ra số lượng số chẵn đếm được
Ví dụ:
Input
5
1 12 4 -3 8
Output
3
Giải thích ví dụ mẫu:
- Ví dụ:
5 1 12 4 -3 8
- Giải thích: Có 3 số chẵn trong dãy: 12, 4, và 8. <br>
1. Hiểu yêu cầu:
Bạn cần đọc một số nguyên dương N
, sau đó đọc N
số nguyên tiếp theo và đếm xem có bao nhiêu số trong dãy đó là số chẵn.
2. Cách xác định số chẵn:
Một số nguyên x
là số chẵn nếu nó chia hết cho 2. Trong C++, bạn có thể kiểm tra điều này bằng toán tử modulo: x % 2 == 0
.
3. Sử dụng thư viện chuẩn C++:
- Để đọc và ghi dữ liệu, bạn sẽ cần thư viện
iostream
. - Để lưu trữ dãy
N
số,vector<int>
là lựa chọn phù hợp và linh hoạt hơn mảng truyền thống. - Để đếm số lượng phần tử thỏa mãn một điều kiện, thư viện
algorithm
cung cấp hàmcount_if
rất tiện lợi.
4. Hướng giải chi tiết (sử dụng vector
và count_if
):
Bước 1: Chuẩn bị:
- Bao gồm các header cần thiết:
iostream
để nhập xuất,vector
để dùngvector
, vàalgorithm
để dùngcount_if
. - Có thể thêm
using namespace std;
. - Để tăng tốc nhập xuất cho
N
lớn, bạn có thể thêm các dòng sau ở đầu hàmmain
:ios_base::sync_with_stdio(false); cin.tie(NULL);
- Bao gồm các header cần thiết:
Bước 2: Đọc N:
- Khai báo một biến kiểu
int
(hoặclong long
nếu N có thể rất lớn, nhưng đề bài cho10^6
nênint
đủ) để lưu giá trịN
. - Đọc giá trị
N
từ đầu vào chuẩn (cin
).
- Khai báo một biến kiểu
Bước 3: Đọc dãy số và lưu vào vector:
- Khai báo một đối tượng
vector<int>
. Kích thước của vector sẽ làN
. - Sử dụng một vòng lặp (ví dụ:
for
) chạyN
lần. - Trong mỗi lần lặp, đọc một số nguyên từ đầu vào chuẩn (
cin
) và thêm số đó vào cuối vector (ví dụ: dùng phương thứcpush_back
).
- Khai báo một đối tượng
Bước 4: Đếm số chẵn:
- Sử dụng hàm
count_if
. Hàm này cần 3 đối số:- Iterator bắt đầu của dãy (
vector.begin()
). - Iterator kết thúc của dãy (
vector.end()
). - Một predicate (một hàm hoặc lambda) nhận một phần tử của vector và trả về
true
nếu phần tử đó thỏa mãn điều kiện (là số chẵn),false
nếu ngược lại. Predicate này có thể là một lambda expression như[](int x){ return x % 2 == 0; }
.
- Iterator bắt đầu của dãy (
- Kết quả trả về của
count_if
chính là số lượng số chẵn. Lưu kết quả này vào một biến kiểuint
.
- Sử dụng hàm
Bước 5: In kết quả:
- In giá trị của biến đếm được ở Bước 4 ra đầu ra chuẩn (
cout
), sau đó là ký tự xuống dòng (endl
).
- In giá trị của biến đếm được ở Bước 4 ra đầu ra chuẩn (
5. Lưu ý về bộ nhớ (N lớn):
Với N
lên đến 10^6
, việc lưu trữ toàn bộ dãy số vào vector
là chấp nhận được về mặt bộ nhớ (khoảng vài MB).
6. Hướng giải thay thế (tiết kiệm bộ nhớ hơn):
Nếu N
lớn hơn nữa và bộ nhớ là vấn đề, bạn có thể không cần lưu toàn bộ dãy số. Thay vào đó:
- Khai báo biến đếm (
count
) khởi tạo bằng 0. - Sử dụng một vòng lặp chạy
N
lần. - Trong mỗi lần lặp, chỉ cần đọc một số từ đầu vào, kiểm tra xem nó có phải là số chẵn không.
- Nếu là số chẵn, tăng biến đếm lên 1.
- Không cần lưu số vừa đọc vào vector hay mảng.
- Sau khi vòng lặp kết thúc, in biến đếm.
Tuy nhiên, với giới hạn
N <= 10^6
của bài này, cách dùng vector +count_if
là đủ hiệu quả và mang tính "std" cao hơn.
Comments