Bài 11.1: Khái niệm và sử dụng mảng 1 chiều trong C++

Bài 11.1: Khái niệm và sử dụng mảng 1 chiều trong C++
Chào mừng trở lại với series blog C++ của FullhouseDev! 👋
Trong cuộc sống hay trong lập trình, chúng ta thường xuyên phải làm việc với những tập hợp các dữ liệu cùng loại. Ví dụ: một danh sách điểm thi của học sinh, nhiệt độ trung bình các ngày trong tuần, hoặc đơn giản là một chuỗi các số nguyên. Việc lưu trữ và quản lý từng giá trị một bằng các biến riêng lẻ sẽ rất cồng kềnh và phi thực tế khi số lượng dữ liệu lớn.
Đây chính là lúc mảng (array) tỏa sáng! Mảng cho phép chúng ta gom các biến cùng kiểu lại với nhau dưới một cái tên duy nhất. Trong bài viết này, chúng ta sẽ tập trung vào loại mảng cơ bản và phổ biến nhất: mảng 1 chiều.
Hãy cùng "mở khóa" sức mạnh của mảng 1 chiều trong C++ nhé!
Mảng 1 Chiều là gì?
Tưởng tượng bạn có một dãy các "ngăn kéo" được đánh số liên tiếp, mỗi ngăn chỉ dùng để chứa một loại đồ vật duy nhất (ví dụ: chỉ quần áo, hoặc chỉ sách). Mảng 1 chiều trong C++ cũng tương tự như vậy!
Một mảng 1 chiều là một tập hợp các phần tử có cùng kiểu dữ liệu, được lưu trữ liên tiếp trong bộ nhớ máy tính và được truy cập thông qua một chỉ số (index).
- Cùng kiểu dữ liệu: Tất cả các phần tử trong mảng phải có kiểu dữ liệu giống nhau (int, float, double, char, string...).
- Lưu trữ liên tiếp: Các phần tử được xếp "sát nhau" trong bộ nhớ, giúp việc truy cập nhanh chóng và hiệu quả.
- Chỉ số (Index): Mỗi phần tử trong mảng có một vị trí duy nhất, được xác định bằng chỉ số của nó. Đặc biệt quan trọng: Trong C++, chỉ số của mảng luôn bắt đầu từ 0.
Ví dụ: Một mảng gồm 5 số nguyên sẽ có các phần tử tại các chỉ số 0, 1, 2, 3, 4.
Khai báo Mảng 1 Chiều
Để sử dụng mảng, trước hết bạn cần "khai báo" nó. Khi khai báo, bạn cần cho C++ biết:
- Kiểu dữ liệu của các phần tử trong mảng.
- Tên mà bạn muốn đặt cho mảng.
- Kích thước (số lượng phần tử tối đa) của mảng.
Cú pháp khai báo mảng 1 chiều cơ bản là:
kieu_du_lieu ten_mang[kich_thuoc];
Trong đó:
kieu_du_lieu
: Kiểu dữ liệu của các phần tử (ví dụ:int
,float
,char
,double
).ten_mang
: Tên định danh bạn đặt cho mảng (tuân theo quy tắc đặt tên biến).kich_thuoc
: Số lượng phần tử mà mảng có thể chứa. Lưu ý quan trọng: Với mảng C-style cơ bản này,kich_thuoc
thường phải là một hằng số nguyên dương hoặc một biểu thức có thể tính toán ra một hằng số nguyên dương tại thời điểm biên dịch (compile-time).
Ví dụ minh họa:
#include <iostream>
int main() {
// Khai báo một mảng 5 số nguyên
int diemSo[5];
// Khai báo một mảng 10 số thực
double nhietDo[10];
// Khai báo một mảng 20 ký tự (có thể dùng cho chuỗi ký tự)
char ten[20];
cout << "Da khai bao xong cac mang." << endl;
return 0;
}
Giải thích: Đoạn code trên chỉ đơn thuần thông báo cho trình biên dịch về sự tồn tại của các mảng diemSo
, nhietDo
, ten
với kích thước và kiểu dữ liệu xác định. Lúc này, các phần tử bên trong mảng có thể chứa những giá trị "rác" (garbage values) từ bộ nhớ.
Khởi tạo Mảng 1 Chiều
Sau khi khai báo, bạn thường muốn "đổ" các giá trị ban đầu vào mảng. Việc này gọi là khởi tạo mảng. Có nhiều cách để khởi tạo:
1. Khởi tạo ngay lúc khai báo (sử dụng danh sách khởi tạo {})
Đây là cách phổ biến và tiện lợi nhất để gán giá trị ban đầu cho mảng. Bạn liệt kê các giá trị trong cặp ngoặc nhọn {}
và phân cách bằng dấu phẩy.
Khởi tạo đầy đủ: Liệt kê đủ số phần tử bằng kích thước đã khai báo.
#include <iostream> int main() { // Khởi tạo đầy đủ 5 số nguyên int diemThi[] = {85, 90, 78, 92, 88}; // Kich thuoc tu dong duoc suy dien la 5 // Hoac co the khai bao ro kich thuoc double giaCa[3] = {15.5, 20.0, 12.75}; cout << "Mảng diemThi co kich thuoc tu dong suy dien tu 5 phan tu." << endl; cout << "Mảng giaCa duoc khai bao ro kich thuoc 3 va khoi tao." << endl; return 0; }
Giải thích:
- Đối với
diemThi
, chúng ta không cần chỉ định kích thước trong[]
. Trình biên dịch C++ sẽ tự động đếm số phần tử trong{}
(ở đây là 5) và gán kích thước đó cho mảng. - Đối với
giaCa
, chúng ta khai báo rõ kích thước là 3 và cung cấp đúng 3 giá trị.
- Đối với
Khởi tạo một phần: Liệt kê ít hơn số phần tử so với kích thước đã khai báo. Các phần tử còn lại sẽ được tự động khởi tạo về giá trị mặc định của kiểu dữ liệu (0 cho số nguyên/thực,
false
cho bool,'\0'
cho char,nullptr
cho con trỏ...).#include <iostream> int main() { // Khai báo 5 số nguyên, chỉ khởi tạo 2 giá trị đầu int soNguyen[5] = {1, 2}; // Khi đó: soNguyen[0]=1, soNguyen[1]=2, soNguyen[2]=0, soNguyen[3]=0, soNguyen[4]=0 // Khai báo 4 số thực, chỉ khởi tạo 1 giá trị đầu float soThuc[4] = {3.14f}; // Khi đó: soThuc[0]=3.14, soThuc[1]=0.0, soThuc[2]=0.0, soThuc[3]=0.0 // Khai báo 6 ký tự, chỉ khởi tạo 3 giá trị đầu char kyTu[6] = {'a', 'b', 'c'}; // Khi đó: kyTu[0]='a', kyTu[1]='b', kyTu[2]='c', kyTu[3]='\0', kyTu[4]='\0', kyTu[5]='\0' cout << "Cac mang da duoc khoi tao mot phan." << endl; return 0; }
Giải thích: Các phần tử không được liệt kê trong danh sách khởi tạo sẽ nhận giá trị 0 (hoặc tương đương với 0 đối với kiểu dữ liệu đó).
Khởi tạo tất cả về 0 (hoặc giá trị mặc định): Nếu bạn chỉ muốn tất cả các phần tử có giá trị mặc định ban đầu, bạn có thể dùng
{}
trống hoặc{0}
.#include <iostream> int main() { // Khai báo và khởi tạo tất cả phần tử về 0 (hoặc gia tri mac dinh) int duLieu[10] = {}; // Hoac {0} cung duoc voi cac kieu so // Tat ca 10 phan tu cua duLieu deu co gia tri 0 double ketQua[5] = {0.0}; // Hoac {} deu duoc // Tat ca 5 phan tu cua ketQua deu co gia tri 0.0 cout << "Cac mang da duoc khoi tao ve gia tri mac dinh." << endl; return 0; }
Giải thích: Dùng
{}
hoặc{0}
là cách gọn gàng để đảm bảo tất cả các phần tử ban đầu có giá trị 0 (hoặc giá trị mặc định tương đương).
2. Khởi tạo (gán giá trị) sau khi khai báo
Bạn có thể gán giá trị cho từng phần tử riêng lẻ sau khi mảng đã được khai báo. Để làm điều này, bạn cần sử dụng chỉ số của phần tử đó.
Cú pháp: ten_mang[chi_so] = gia_tri;
Ví dụ minh họa:
#include <iostream>
int main() {
int diemSo[5]; // Khai bao mang 5 phan tu (gia tri ban dau la rac)
// Gan gia tri cho tung phan tu
diemSo[0] = 95; // Phan tu dau tien (chi so 0)
diemSo[1] = 87; // Phan tu thu hai (chi so 1)
diemSo[2] = 91; // Phan tu thu ba (chi so 2)
diemSo[3] = 76; // Phan tu thu tu (chi so 3)
diemSo[4] = 93; // Phan tu cuoi cung (chi so 4 = kich_thuoc - 1)
cout << "Da gan gia tri cho tung phan tu cua mang diemSo." << endl;
return 0;
}
Giải thích: Chúng ta truy cập vào từng "ngăn kéo" của mảng thông qua chỉ số của nó (bắt đầu từ 0) và gán một giá trị mới vào đó.
Truy cập Phần tử trong Mảng
Điểm mạnh của mảng là khả năng truy cập trực tiếp vào bất kỳ phần tử nào thông qua chỉ số của nó. Bạn có thể dùng chỉ số để:
- Đọc giá trị của phần tử đó.
- Thay đổi giá trị của phần tử đó.
Cú pháp: ten_mang[chi_so]
Ví dụ minh họa:
#include <iostream>
int main() {
int diemThi[] = {85, 90, 78, 92, 88}; // Mang 5 phan tu
// Doc gia tri cua cac phan tu
cout << "Diem cua hoc sinh thu nhat (index 0): " << diemThi[0] << endl;
cout << "Diem cua hoc sinh thu ba (index 2): " << diemThi[2] << endl;
cout << "Diem cua hoc sinh cuoi cung (index 4): " << diemThi[4] << endl; // Chi so cuoi = kich thuoc - 1
// Thay doi gia tri cua mot phan tu
diemThi[1] = 95; // Thay doi diem cua hoc sinh thu hai (index 1) tu 90 thanh 95
cout << "Diem cua hoc sinh thu hai sau khi cap nhat: " << diemThi[1] << endl;
return 0;
}
Giải thích: Chúng ta sử dụng diemThi[chỉ_số]
để tham chiếu đến phần tử mong muốn. diemThi[0]
là phần tử đầu tiên, diemThi[2]
là phần tử thứ ba, v.v. Sau đó, chúng ta có thể sử dụng biểu thức này giống như một biến thông thường để đọc giá trị (như khi in ra màn hình) hoặc gán giá trị mới.
Lưu ý cực kỳ quan trọng: Hãy luôn đảm bảo chỉ số bạn sử dụng nằm trong phạm vi hợp lệ của mảng, tức là từ 0
đến kich_thuoc - 1
. Truy cập vào chỉ số ngoài phạm vi này (gọi là array out of bounds) là một lỗi nghiêm trọng, có thể dẫn đến chương trình bị crash hoặc cho kết quả không mong muốn (hành vi không xác định - undefined behavior).
Duyệt (Iterate) qua Mảng
Để xử lý tất cả hoặc một phần các phần tử trong mảng, bạn cần "duyệt" qua nó. Vòng lặp là công cụ lý tưởng cho việc này. Hai cách phổ biến nhất để duyệt mảng 1 chiều là sử dụng vòng lặp for
truyền thống với chỉ số hoặc vòng lặp range-based for
(từ C++11).
1. Duyệt bằng vòng lặp for
với chỉ số
Cách này cho phép bạn truy cập cả chỉ số lẫn giá trị của từng phần tử.
#include <iostream>
int main() {
int soChan[] = {2, 4, 6, 8, 10};
int kichThuoc = 5; // Can biet kich thuoc cua mang
cout << "Cac phan tu cua mang soChan (su dung for index):" << endl;
// Duyet tu phan tu dau tien (index 0) den phan tu cuoi cung (index kichThuoc-1)
for (int i = 0; i < kichThuoc; ++i) {
cout << "Phan tu tai index " << i << ": " << soChan[i] << endl;
}
return 0;
}
Giải thích:
- Chúng ta dùng một biến đếm
i
bắt đầu từ 0. - Vòng lặp tiếp tục chạy miễn là
i
nhỏ hơnkichThuoc
. Điều này đảm bảoi
sẽ chạy từ 0 đếnkichThuoc - 1
, bao phủ tất cả các chỉ số hợp lệ của mảng. - Trong mỗi lần lặp, chúng ta sử dụng
soChan[i]
để truy cập phần tử tại chỉ số hiện tại.
2. Duyệt bằng vòng lặp range-based for
(từ C++11)
Cách này đơn giản hơn khi bạn chỉ cần truy cập giá trị của từng phần tử và không cần quan tâm đến chỉ số của nó.
#include <iostream>
#include <string> // Can include de dung string
int main() {
string danhSachTen[] = {"Alice", "Bob", "Charlie"};
cout << "Cac ten trong danh sach (su dung range-based for):" << endl;
// Duyet qua tung phan tu trong mang danhSachTen
for (string ten : danhSachTen) {
cout << "Ten: " << ten << endl;
}
// Ban co the su dung tu khoa auto de don gian hon
int diemSo[] = {85, 90, 78, 92, 88};
cout << "\nCac diem so (su dung range-based for voi auto):" << endl;
for (auto diem : diemSo) {
cout << "Diem: " << diem << endl;
}
return 0;
}
Giải thích:
for (string ten : danhSachTen)
đọc là: "Với mỗistring
ten
trong mảngdanhSachTen
..."- Trong mỗi lần lặp, biến
ten
sẽ lần lượt nhận giá trị của từng phần tử trong mảng. - Cú pháp này gọn gàng và ít bị lỗi "duyệt sai cận" (off-by-one error) so với vòng lặp
for
truyền thống khi chỉ cần đọc giá trị. - Sử dụng
auto
giúp C++ tự động suy luận kiểu dữ liệu của phần tử.
Lấy Kích thước của Mảng C-style
Một điều quan trọng cần biết là mảng C-style không tự động lưu trữ kích thước của nó. Điều này có nghĩa là nếu bạn truyền mảng vào một hàm, hàm đó sẽ không biết kích thước ban đầu của mảng trừ khi bạn truyền kích thước đó như một tham số riêng.
Tuy nhiên, khi mảng được khai báo trong cùng một phạm vi (scope) mà bạn đang làm việc, bạn có thể tính toán kích thước của nó bằng cách sử dụng toán tử sizeof
.
Công thức phổ biến để tính số phần tử:
so_phan_tu = sizeof(ten_mang) / sizeof(ten_mang[0])
sizeof(ten_mang)
: Trả về tổng số byte mà toàn bộ mảng chiếm trong bộ nhớ.sizeof(ten_mang[0])
: Trả về số byte mà một phần tử đơn lẻ của mảng chiếm trong bộ nhớ.
Chia tổng số byte cho số byte của một phần tử sẽ ra tổng số phần tử.
Ví dụ minh họa:
#include <iostream>
#include <cstddef> // Can dung cho size_t
int main() {
int duLieu[] = {10, 20, 30, 40, 50, 60}; // Kich thuoc tu dong suy dien
// Tinh kich thuoc (so luong phan tu) cua mang
size_t kichThuocMang = sizeof(duLieu) / sizeof(duLieu[0]);
cout << "Tong so byte cua mang duLieu: " << sizeof(duLieu) << endl;
cout << "So byte cua mot phan tu (int): " << sizeof(duLieu[0]) << endl;
cout << "Kich thuoc cua mang (so phan tu): " << kichThuocMang << endl;
// Su dung kich thuoc de duyet mang
cout << "\nCac phan tu trong mang:" << endl;
for (size_t i = 0; i < kichThuocMang; ++i) {
cout << duLieu[i] << " ";
}
cout << endl;
// Vi du voi mang double
double nhietDo[] = {22.5, 23.0, 21.8};
size_t kichThuocNhietDo = sizeof(nhietDo) / sizeof(nhietDo[0]);
cout << "\nKich thuoc cua mang nhietDo (so phan tu): " << kichThuocNhietDo << endl;
return 0;
}
Giải thích: Chúng ta sử dụng sizeof
để tính toán kích thước động (tại thời điểm biên dịch) dựa trên số phần tử thực tế đã được khởi tạo. Kiểu dữ liệu size_t
là kiểu không âm được sử dụng phổ biến cho kích thước và chỉ số trong C++.
Lưu ý: Từ C++17 trở đi, bạn có thể sử dụng size(ten_mang)
để lấy kích thước một cách an toàn hơn, nhưng sizeof
là cách truyền thống và vẫn hoạt động tốt cho mảng C-style khi nằm trong cùng phạm vi.
Ví dụ Tổng hợp: Nhập và Tính Tổng các Số trong Mảng
Hãy cùng áp dụng những gì đã học để viết một chương trình nhỏ: yêu cầu người dùng nhập một số lượng nhất định các số nguyên, lưu chúng vào mảng, và sau đó tính tổng của các số đó.
#include <iostream> // Cho cin, cout, endl
int main() {
const int MAX_SIZE = 100; // Dinh nghia kich thuoc toi da co dinh
int cacSo[MAX_SIZE]; // Khai bao mang voi kich thuoc toi da
int soLuong; // So luong thuc te nguoi dung muon nhap
cout << "Nhap so luong so nguyen ban muon nhap (toi da " << MAX_SIZE << "): ";
cin >> soLuong;
// Kiem tra dau vao hop le
if (soLuong <= 0 || soLuong > MAX_SIZE) {
cout << "So luong khong hop le. Chuong trinh ket thuc." << endl;
return 1; // Bao loi
}
// Nhap cac phan tu vao mang
cout << "Moi ban nhap " << soLuong << " so nguyen:" << endl;
for (int i = 0; i < soLuong; ++i) {
cout << "Nhap so thu " << (i + 1) << ": ";
cin >> cacSo[i]; // Doc gia tri vao phan tu thu i
}
// Tinh tong cac phan tu trong mang
int tong = 0;
for (int i = 0; i < soLuong; ++i) {
tong += cacSo[i]; // Cong gia tri cua phan tu thu i vao tong
}
// In ket qua
cout << "\nCac so ban da nhap la:";
for (int i = 0; i < soLuong; ++i) {
cout << " " << cacSo[i];
}
cout << endl;
cout << "Tong cua cac so trong mang la: " << tong << endl;
return 0;
}
Giải thích:
- Chúng ta khai báo mảng
cacSo
với kích thước tối đa cố định (MAX_SIZE = 100
). - Biến
soLuong
lưu trữ số lượng phần tử thực tế mà người dùng muốn nhập, có thể nhỏ hơn hoặc bằngMAX_SIZE
. - Vòng lặp
for
đầu tiên dùng để đọc dữ liệu từ bàn phím và lưu vào mảng. Chúng ta lặp từi = 0
đếnsoLuong - 1
.cacSo[i]
là nơi dữ liệu được lưu. - Vòng lặp
for
thứ hai dùng để tính tổng. Chúng ta lặp qua các phần tử đã nhập (từ 0 đếnsoLuong - 1
) và cộng giá trị của chúng vào biếntong
. - Cuối cùng, chúng ta in ra các số đã nhập và tổng của chúng. Lưu ý vòng lặp in cũng chỉ duyệt
soLuong
phần tử thực tế.
Ví dụ Khác: Tìm Phần tử Lớn nhất trong Mảng
Một bài toán phổ biến khác là tìm giá trị lớn nhất (hoặc nhỏ nhất) trong mảng.
#include <iostream> // Cho cout, endl
#include <limits> // Cho numeric_limits
int main() {
int diemThi[] = {85, 90, 78, 92, 88, 95, 81};
int kichThuoc = sizeof(diemThi) / sizeof(diemThi[0]); // Tinh kich thuoc mang
// Khởi tạo bien luu phan tu lon nhat
// Ban dau gan gia tri cua phan tu dau tien hoac gia tri rat nho
int maxDiem = diemThi[0];
// Hoac khoi tao bang gia tri nho nhat co the cua kieu int:
// int maxDiem = numeric_limits<int>::min();
// Duyet qua mang de tim gia tri lon nhat
// Bat dau tu phan tu thu hai (index 1) neu da khoi tao maxDiem = diemThi[0]
for (int i = 1; i < kichThuoc; ++i) {
if (diemThi[i] > maxDiem) {
maxDiem = diemThi[i]; // Cap nhat gia tri lon nhat neu tim thay phan tu lon hon
}
}
cout << "Mang diem thi: ";
for (int i = 0; i < kichThuoc; ++i) {
cout << diemThi[i] << (i == kichThuoc - 1 ? "" : ", ");
}
cout << endl;
cout << "Diem cao nhat trong mang la: " << maxDiem << endl;
return 0;
}
Giải thích:
- Chúng ta khởi tạo
maxDiem
bằng giá trị của phần tử đầu tiên của mảng. Điều này đảm bảo rằngmaxDiem
ban đầu là một giá trị hợp lệ từ mảng. - Vòng lặp bắt đầu từ phần tử thứ hai (index 1) vì phần tử đầu tiên đã dùng để khởi tạo
maxDiem
. - Trong mỗi lần lặp, chúng ta so sánh phần tử hiện tại (
diemThi[i]
) vớimaxDiem
đang có. - Nếu phần tử hiện tại lớn hơn, chúng ta cập nhật
maxDiem
bằng giá trị của phần tử đó. - Sau khi duyệt hết mảng,
maxDiem
sẽ chứa giá trị lớn nhất tìm được.
Bài tập ví dụ: C++ Bài 7.A1: Phần tử lớn nhất(1)
Viết chương trình để tìm phần tử lớn nhất của một mảng số nguyên có \(N\) phần tử cho trước.
INPUT FORMAT
Dòng đầu tiên chứa số nguyên \(N\) \((1 \leq N \leq 10^6)\) là số lương phần tử
Dòng thứ hai chứa \(N\) số nguyên mỗi số cách nhau một dấu cách là các phần tử của mảng.
OUTPUT FORMAT
In ra số nguyên là phần tử lớn nhất của mảng
Ví dụ:
Input
6
1 4 1 6 12 9
Output
12
Giải thích ví dụ mẫu:
Ví dụ:
- Giải thích: Tìm số lớn nhất trong mảng
1 4 1 6 12 9
là12
. <br>
Đọc dữ liệu đầu vào:
- Đầu tiên, bạn cần đọc số nguyên
N
từ đầu vào chuẩn (sử dụngcin
). - Tiếp theo, bạn cần đọc
N
số nguyên tiếp theo. VìN
có thể lên tới 10^6, bạn nên sử dụng một cấu trúc dữ liệu có thể thay đổi kích thước động để lưu trữ các số này, ví dụ nhưvector
. Bạn có thể khai báo mộtvector
có kích thướcN
và sau đó đọcN
số lần lượt vào các phần tử của vector.
- Đầu tiên, bạn cần đọc số nguyên
Tìm phần tử lớn nhất:
- Cách truyền thống là duyệt qua toàn bộ mảng (hoặc vector), giữ một biến tạm để lưu giá trị lớn nhất tìm thấy cho đến nay. Khởi tạo biến này với giá trị của phần tử đầu tiên (hoặc một giá trị rất nhỏ). Sau đó, với mỗi phần tử tiếp theo, so sánh nó với giá trị lớn nhất hiện tại và cập nhật nếu cần.
- Tuy nhiên, trong C++, thư viện chuẩn (
std
) cung cấp hàm rất tiện lợi để làm việc này cho một dãy (range). Bạn nên sử dụng hàmmax_element
, có trong header<algorithm>
. Hàm này nhận hai iterator (chỉ vị trí bắt đầu và kết thúc của dãy) và trả về một iterator trỏ đến phần tử lớn nhất trong dãy đó. - Với
vector
, bạn có thể dễ dàng lấy iterator bắt đầu bằngtên_vector.begin()
và iterator kết thúc bằngtên_vector.end()
.
Lấy giá trị và In kết quả:
- Hàm
max_element
trả về một iterator. Để lấy được giá trị thực sự của phần tử mà iterator đó trỏ tới, bạn cần sử dụng toán tử giải tham chiếu (*
). - Cuối cùng, in giá trị lớn nhất tìm được ra màn hình chuẩn (sử dụng
cout
).
- Hàm
Tóm tắt các bước chính và các thành phần cần sử dụng:
- Sử dụng
cin
để đọcN
và các phần tử mảng. - Sử dụng
vector<int>
để lưu trữN
số nguyên. - Sử dụng
max_element
(từ header<algorithm>
) vớivector.begin()
vàvector.end()
để tìm iterator tới phần tử lớn nhất. - Sử dụng toán tử
*
để lấy giá trị từ iterator. - Sử dụng
cout
để in kết quả. - Cần include các header
<iostream>
,<vector>
,<algorithm>
.
Comments