KIỂU BẢN GHI TRONG C++
Chào các bạn học sinh và lập trình viên! Khi giải quyết các bài toán quản lý dữ liệu thực tế trên hệ thống Sen Code Online Judge! hoặc trong các đề thi Học sinh giỏi Tin học, chúng ta thường phải xử lý các đối tượng chứa nhiều thông tin phức tạp.
Ví dụ: Để quản lý một học sinh, ta cần lưu trữ: Họ tên (xâu ký tự), Tuổi (số nguyên), Điểm trung bình (số thực). Nếu sử dụng các mảng riêng biệt cho từng thông tin, chương trình sẽ rất khó viết, dễ nhầm lẫn chỉ số và cực kỳ phức tạp khi cần sắp xếp danh sách.
Để giải quyết triệt để vấn đề này, C++ cung cấp Kiểu bản ghi (struct), cho phép chúng ta tự định nghĩa một kiểu dữ liệu mới gom nhóm nhiều thuộc tính khác nhau lại thành một thể thống nhất. Bài viết này sẽ hướng dẫn chi tiết cú pháp và cách áp dụng kiểu dữ liệu bản ghi này.
1. Khái niệm kiểu bản ghi (struct)
Kiểu bản ghi (cấu trúc - struct) là một kiểu dữ liệu do người dùng tự định nghĩa, cho phép kết hợp các biến thuộc các kiểu dữ liệu khác nhau (số nguyên, số thực, ký tự, xâu ký tự, mảng...) lại dưới một tên gọi duy nhất. Mỗi biến thành phần bên trong cấu trúc được gọi là một Trường dữ liệu (Field) hoặc Thành viên (Member).
2. Cú pháp khai báo bản ghi
Cú pháp:
struct Tên_Bản_Ghi {
kiểu_dữ_liệu_1 tên_trường_1;
kiểu_dữ_liệu_2 tên_trường_2;
...
}; // CHÚ Ý: Bắt buộc phải có dấu chấm phẩy ở cuối
Ví dụ minh họa:
Khai báo một kiểu bản ghi để quản lý thông tin của một Học sinh:
struct HocSinh {
string maSo; // Mã số học sinh
string hoTen; // Họ và tên
int tuoi; // Tuổi
double diemTB; // Điểm trung bình học tập
}; // Đã khai báo xong một kiểu dữ liệu mới tên là HocSinh
3. Khai báo biến và truy xuất các trường của bản ghi
a. Khai báo biến kiểu bản ghi:
Sau khi đã định nghĩa cấu trúc, ta sử dụng Tên_Bản_Ghi như một kiểu dữ liệu thông thường (giống như int, double, string...) để khai báo biến:
HocSinh hs1; // Khai báo một học sinh tên hs1
HocSinh hs2 = {"HS01", "Nguyen Van A", 16, 9.25}; // Khai báo và khởi tạo trực tiếp
b. Truy xuất các trường dữ liệu (Đọc / Ghi):
Để truy cập vào các trường thành viên của một biến bản ghi, ta sử dụng Toán tử dấu chấm (.):
tên_biến_bản_ghi.tên_trường
Ví dụ minh họa truy xuất:
#include <bits/stdc++.h>
using namespace std;
// Khai báo struct ở ngoài hàm main để dùng chung toàn chương trình
struct HocSinh {
string maSo;
string hoTen;
int tuoi;
double diemTB;
};
int main() {
HocSinh hs;
// Ghi dữ liệu vào các trường của bản ghi hs
hs.maSo = "HS102";
hs.hoTen = "Tran Thi B";
hs.tuoi = 17;
hs.diemTB = 9.5;
// Đọc dữ liệu từ bản ghi hs và in ra màn hình
cout << "Ma so: " << hs.maSo << "\n";
cout << "Ho ten: " << hs.hoTen << "\n";
cout << "Tuoi: " << hs.tuoi << "\n";
cout << "Diem TB: " << hs.diemTB << "\n";
return 0;
}
4. Mảng bản ghi và Sắp xếp mảng bản ghi (Rất quan trọng)
Trong các bài toán lập trình thi đấu, ta thường không quản lý một biến đơn lẻ mà quản lý một mảng gồm N bản ghi (ví dụ danh sách N thí sinh, N cuốn sách, N mặt hàng...).
Khai báo mảng bản ghi:
vector<HocSinh> danhSach(N); // Khai báo mảng động vector chứa N học sinh
Sắp xếp mảng bản ghi bằng hàm sort tự định nghĩa:
Hàm sort mặc định của C++ không tự hiểu cách so sánh hai đối tượng phức tạp như HocSinh (nên so sánh theo điểm số hay theo họ tên?).
Do đó, chúng ta phải viết một Hàm so sánh (Comparator) để hướng dẫn sort cách sắp xếp.
Ví dụ minh họa hoàn chỉnh:
Nhập vào danh sách gồm N học sinh. Sắp xếp danh sách học sinh theo thứ tự Điểm trung bình giảm dần. In ra danh sách sau khi sắp xếp.
#include <bits/stdc++.h>
using namespace std;
struct HocSinh {
string maSo;
string hoTen;
double diemTB;
};
// Hàm so sánh tự định nghĩa (Comparator)
// Trả về true nếu học sinh a đứng trước học sinh b
bool soSanhHocSinh(HocSinh a, HocSinh b) {
return a.diemTB > b.diemTB; // Sắp xếp điểm trung bình giảm dần
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int N;
cin >> N;
vector<HocSinh> ds(N);
for (int i = 0; i < N; ++i) {
cin >> ds[i].maSo;
cin.ignore(); // Xóa khoảng trắng thừa trước khi đọc chuỗi có dấu cách
getline(cin, ds[i].hoTen);
cin >> ds[i].diemTB;
}
// Gọi hàm sort truyền thêm hàm so sánh tự định nghĩa
sort(ds.begin(), ds.end(), soSanhHocSinh);
cout << "--- BANG XEP HANG DIEM SO ---\n";
for (int i = 0; i < N; ++i) {
cout << ds[i].maSo << " | " << ds[i].hoTen << " | " << ds[i].diemTB << "\n";
}
return 0;
}
5. ĐỀ XUẤT 10 BÀI TẬP THỰC HÀNH THỰC TẾ (KIỂU BẢN GHI)
Hãy thực hiện đọc/ghi dữ liệu bằng tệp thông qua freopen đối với 10 bài tập ứng dụng thực tế đa dạng lĩnh vực sau đây:
Bài 1: Bảng xếp hạng điểm số thí sinh
- Ngữ cảnh: Hệ thống Sen Code Online Judge! cần thống kê danh sách xếp hạng thí sinh của một kỳ thi. Mỗi thí sinh gồm các thông tin: Mã thí sinh (chuỗi không chứa dấu cách), Họ tên (chuỗi chứa dấu cách), Số bài giải đúng (số nguyên).
- Yêu cầu: Đọc dữ liệu từ tệp
ranking.inp. Hãy sắp xếp danh sách thí sinh giảm dần theo số bài giải đúng. Nếu hai thí sinh có cùng số bài giải đúng bằng nhau, hãy ưu tiên xếp thí sinh có Mã số nhỏ hơn theo thứ tự từ điển đứng trước. Ghi kết quả ra tệpranking.out. - Đầu vào (Input - tệp
ranking.inp):- Dòng 1 chứa số nguyên dương N (1 <= N <= 104).
- 3 * N dòng tiếp theo, mỗi nhóm 3 dòng chứa thông tin của một thí sinh: Mã số, Họ tên, Số bài đúng.
- Đầu ra (Output - tệp
ranking.out): Danh sách thí sinh sau khi sắp xếp, mỗi thí sinh gồm Mã số, Họ tên, Số bài đúng cách nhau bởi dấu gạch đứng|. Ví dụ:
Input:
3 user02 Tran Van B 5 user01 Nguyen Van A 5 user03 Le Thi C 3Output:
user01 | Nguyen Van A | 5 user02 | Tran Van B | 5 user03 | Le Thi C | 3
Bài 2: Doanh thu bán sách nhà sách
- Ngữ cảnh: Một nhà sách quản lý thông tin các quyển sách bán ra trong tháng. Mỗi quyển sách gồm: Tên sách (chuỗi chứa khoảng trắng), Giá bán lẻ (số nguyên), Số lượng đã bán (số nguyên).
- Yêu cầu: Đọc dữ liệu từ tệp
book.inp. Tính tổng doanh thu của nhà sách trong tháng và tìm quyển sách đem lại doanh thu lớn nhất (Doanh thu = Giá bán * Số lượng bán). Ghi kết quả ra tệpbook.out. - Đầu vào (Input - tệp
book.inp):- Dòng 1 chứa số nguyên N (1 <= N <= 1000).
- N nhóm dòng tiếp theo, mỗi nhóm gồm 3 dòng: Tên sách, Giá bán, Số lượng bán.
- Đầu ra (Output - tệp
book.out):- Dòng 1 ghi một số nguyên duy nhất là tổng doanh thu của tất cả các sách.
- Dòng 2 ghi tên quyển sách đem lại doanh thu cao nhất.
Ví dụ:
Input:
2 Lap Trinh C++ Can Ban 150000 10 Cau Truc Du Lieu Va Giai Thuat 180000 20Output:
5100000 Cau Truc Du Lieu Va Giai Thuat
Bài 3: Nhân viên kỳ cựu của công ty
- Ngữ cảnh: Một công ty cần tìm nhân viên lớn tuổi nhất để tổ chức vinh danh nhân sự kỳ cựu. Thông tin mỗi nhân viên gồm: Mã nhân viên, Họ tên, Năm sinh (số nguyên).
- Yêu cầu: Đọc thông tin từ tệp
employee.inp, tìm nhân viên lớn tuổi nhất (có năm sinh nhỏ nhất) và ghi ra tệpemployee.out. - Đầu vào (Input - tệp
employee.inp):- Dòng 1 chứa số nguyên N (1 <= N <= 1000).
- N nhóm dòng tiếp theo, mỗi nhóm gồm 3 dòng: Mã nhân viên, Họ tên, Năm sinh.
- Đầu ra (Output - tệp
employee.out): Mã nhân viên và Họ tên của nhân viên lớn tuổi nhất cách nhau bởi dấu cách (nếu có nhiều người có cùng năm sinh nhỏ nhất, in ra người đầu tiên xuất hiện trong danh sách). Ví dụ:
Input:
2 NV01 Nguyen Van Nam 1985 NV02 Tran Thi Hoa 1980Output:
NV02 Tran Thi Hoa
Bài 4: Kiểm tra hàng hết hạn sử dụng
- Ngữ cảnh: Quản lý kho hàng siêu thị cần lọc ra các mặt hàng đã hết hạn sử dụng tính đến ngày X cho trước để loại bỏ khỏi kệ hàng. Mỗi mặt hàng gồm: Mã hàng, Tên hàng, Ngày hết hạn (lưu dưới dạng số nguyên định dạng YYYYMMDD, ví dụ ngày 15 tháng 8 năm 2026 sẽ lưu là
20260815). - Yêu cầu: Đọc danh sách hàng hóa và ngày hiện tại X từ tệp
inventory.inp. Lọc ra các sản phẩm đã hết hạn sử dụng (ngày hết hạn < X). Ghi danh sách ra tệpinventory.out. - Đầu vào (Input - tệp
inventory.inp):- Dòng 1 chứa số nguyên N và số nguyên X (1 <= N <= 1000, X là ngày định dạng YYYYMMDD).
- N nhóm dòng tiếp theo, mỗi nhóm gồm 3 dòng: Mã hàng, Tên hàng, Ngày hết hạn.
- Đầu ra (Output - tệp
inventory.out): Danh sách các mặt hàng hết hạn, mỗi dòng ghi Mã hàng và Tên hàng. Ví dụ:
Input:
2 20260731 MH01 Sua tuoi tiet trung 20260715 MH02 Banh bong lan 20260810Output:
MH01 Sua tuoi tiet trung
Bài 5: Lọc danh sách bệnh nhân sốt cao
- Ngữ cảnh: Một bệnh viện cần theo dõi các bệnh nhân có triệu chứng sốt cao. Mỗi bệnh nhân gồm: Mã bệnh nhân, Họ tên, Nhiệt độ cơ thể (số thực). Bệnh nhân bị sốt nếu nhiệt độ từ 37.5 độ C trở lên.
- Yêu cầu: Đọc dữ liệu bệnh nhân từ tệp
patient.inp. Lọc ra các bệnh nhân bị sốt và sắp xếp danh sách theo nhiệt độ giảm dần. Ghi kết quả ra tệppatient.out. - Đầu vào (Input - tệp
patient.inp):- Dòng 1 chứa số nguyên N (1 <= N <= 1000).
- N nhóm dòng tiếp theo, mỗi nhóm gồm 3 dòng: Mã bệnh nhân, Họ tên, Nhiệt độ.
- Đầu ra (Output - tệp
patient.out): Danh sách bệnh nhân sốt, mỗi dòng gồm Mã bệnh nhân, Họ tên, Nhiệt độ (lấy 1 chữ số thập phân). Ví dụ:
Input:
3 BN01 Nguyen Van A 37.0 BN02 Tran Thi B 38.2 BN03 Le Van C 37.8Output:
BN02 | Tran Thi B | 38.2 BN03 | Le Van C | 37.8
Bài 6: Tọa độ chòm sao gần Trái Đất nhất
- Ngữ cảnh: Trên bản đồ không gian 2D Oxy với Trái Đất nằm ở gốc tọa độ (0, 0), một nhà thiên văn học quan sát N ngôi sao. Mỗi ngôi sao gồm: Tên ngôi sao, hoành độ x (số nguyên), tung độ y (số nguyên).
- Yêu cầu: Đọc dữ liệu từ tệp
star.inp. Tính khoảng cách Euclid từ mỗi ngôi sao tới Trái Đất (0, 0) bằng công thức: khoảngcách = cănbậc_hai(x2 + y2). Hãy tìm và ghi tên ngôi sao nằm gần Trái Đất nhất ra tệpstar.out. - Đầu vào (Input - tệp
star.inp):- Dòng 1 chứa số nguyên N (1 <= N <= 1000).
- N nhóm dòng tiếp theo, mỗi nhóm gồm 2 dòng: Tên ngôi sao, hai số nguyên x và y.
- Đầu ra (Output - tệp
star.out): Tên ngôi sao gần nhất và khoảng cách làm tròn 2 chữ số thập phân. Ví dụ:
Input:
2 Sao Kim 3 4 Sao Hoa 5 12Output:
Sao Kim 5.00
Bài 7: Đặt vé máy bay giá rẻ khởi hành sớm
- Ngữ cảnh: Khách hàng muốn tìm chuyến bay giá rẻ nhất từ Hà Nội đi TP.HCM. Thông tin mỗi chuyến bay gồm: Số hiệu chuyến bay (chuỗi không khoảng trắng), Giờ khởi hành (chuỗi định dạng
HH:MM), Giá vé (số nguyên). - Yêu cầu: Đọc dữ liệu từ tệp
flight.inp. Tìm chuyến bay có giá vé rẻ nhất. Nếu có nhiều chuyến bay có cùng giá vé rẻ nhất bằng nhau, hãy ưu tiên chọn chuyến bay có giờ khởi hành sớm nhất. Ghi kết quả ra tệpflight.out. - Đầu vào (Input - tệp
flight.inp):- Dòng 1 chứa số nguyên N (1 <= N <= 1000).
- N nhóm dòng tiếp theo, mỗi nhóm gồm 3 dòng: Số hiệu, Giờ bay, Giá vé.
- Đầu ra (Output - tệp
flight.out): Số hiệu và Giờ bay của chuyến bay được chọn. Ví dụ:
Input:
3 VN102 08:30 1200000 VJ204 06:15 1200000 QH305 12:00 1500000Output:
VJ204 06:15
Bài 8: Bảng vàng trao huy chương Marathon
- Ngữ cảnh: Kết quả của giải chạy việt dã marathon ghi nhận thời gian về đích của các vận động viên. Thông tin mỗi vận động viên gồm: Mã số (chuỗi), Họ tên, Thời gian chạy (số nguyên biểu thị số giây).
- Yêu cầu: Đọc dữ liệu từ tệp
marathon.inp. Tìm và in ra thông tin 3 vận động viên đạt huy chương Vàng (thời gian chạy nhanh nhất/nhỏ nhất), Bạc (nhanh nhì), Đồng (nhanh ba) ra tệpmarathon.out. - Đầu vào (Input - tệp
marathon.inp):- Dòng 1 chứa số nguyên N (3 <= N <= 1000).
- N nhóm dòng tiếp theo, mỗi nhóm gồm 3 dòng: Mã số, Họ tên, Thời gian chạy (giây).
- Đầu ra (Output - tệp
marathon.out): 3 dòng tương ứng là thông tin của VĐV đạt giải Vàng, Bạc, Đồng theo định dạng:Giải | Mã số | Họ tên | Thời gian. Ví dụ:
Input:
3 VDV01 Nguyen Anh 7200 VDV02 Tran Binh 7100 VDV03 Le Cuong 7500Output:
Gold | VDV02 | Tran Binh | 7100 Silver | VDV01 | Nguyen Anh | 7200 Bronze | VDV03 | Le Cuong | 7500
Bài 9: Chu vi đa giác lồi
- Ngữ cảnh: Một đa giác lồi N đỉnh trên mặt phẳng Oxy được mô tả bằng danh sách tọa độ các đỉnh theo đúng chiều kim đồng hồ. Mỗi đỉnh gồm: Tên đỉnh (1 ký tự), Tọa độ x (số nguyên), Tọa độ y (số nguyên).
- Yêu cầu: Đọc từ tệp
polygon.inp, tính chu vi của đa giác bằng cách cộng khoảng cách giữa các đỉnh liên tiếp (đỉnh 0 đến đỉnh 1, đỉnh 1 đến 2, ..., và đỉnh N-1 quay lại đỉnh 0). Ghi kết quả ra tệppolygon.out. - Đầu vào (Input - tệp
polygon.inp):- Dòng 1 chứa số nguyên N (3 <= N <= 100).
- N dòng tiếp theo, mỗi dòng chứa ký tự Tên đỉnh và hai số nguyên x, y.
- Đầu ra (Output - tệp
polygon.out): Một số thực duy nhất là chu vi đa giác (lấy chính xác 2 chữ số thập phân). Ví dụ:
Input:
4 A 0 0 B 0 3 C 4 3 D 4 0Output:
14.00(Các khoảng cách là AB = 3, BC = 4, CD = 3, DA = 4 -> Tổng = 14)
Bài 10: Tối ưu hóa giỏ hàng siêu thị
- Ngữ cảnh: Một khách hàng đi siêu thị mua sắm với ngân sách tối đa là M đồng. Trong giỏ hàng đã chọn N sản phẩm. Mỗi sản phẩm gồm: Tên sản phẩm, Đơn giá (số nguyên), Số lượng muốn mua (số nguyên).
- Yêu cầu: Đọc thông tin từ tệp
shopping.inp. Kiểm tra xem tổng giá trị giỏ hàng có vượt quá ngân sách M hay không.- Nếu không vượt quá, ghi ra số tiền còn dư.
- Nếu vượt quá ngân sách, hãy ghi ra số tiền thiếu, đồng thời chỉ ra tên sản phẩm có đơn giá cao nhất trong giỏ hàng để khách hàng cân nhắc bỏ lại nhằm tiết kiệm chi phí.
- Đầu vào (Input - tệp
shopping.inp):- Dòng 1 chứa số nguyên N và ngân sách M (1 <= N <= 100, M <= 109).
- N nhóm dòng tiếp theo, mỗi nhóm gồm 3 dòng: Tên sản phẩm, Đơn giá, Số lượng.
- Đầu ra (Output - tệp
shopping.out):- Nếu đủ tiền, ghi:
DU X DONG(với X là số tiền dư). - Nếu thiếu tiền, ghi 2 dòng: dòng 1 ghi
THIEU Y DONG(với Y là số tiền thiếu), dòng 2 ghi tên sản phẩm có đơn giá đắt nhất.
- Nếu đủ tiền, ghi:
Ví dụ:
Input:
2 200000 Dau an Neptune 2L 120000 2 Gao Thom ST25 190000 1Output:
THIEU 230000 DONG Gao Thom ST25
Lời kết
Kiểu bản ghi struct là một vũ khí cực kỳ mạnh mẽ giúp bạn cấu trúc hóa dữ liệu một cách khoa học, sạch sẽ và chuyên nghiệp. Hãy rèn luyện kỹ năng tự định nghĩa các hàm so sánh (Comparator) vì đây là kỹ thuật then chốt xuất hiện liên tục trong lập trình thi đấu thực tế.
Hãy lập trình và nộp bài giải của 10 bài tập kiểu bản ghi trên hệ thống Sen Code Online Judge! nhé! Chúc các bạn học tập thật tốt!
Bình luận