Bài viết này giới thiệu một số ví dụ thực tế về việc sử dụng kiểu dữ liệu cấu trúc (struct) và kiểu liệt kê (enum) trong ngôn ngữ lập trình C. Các ví dụ bao gồm quản lý thông tin sách, xử lý ngày tháng, quản lý tài khoản người dùng và danh bạ liên lạc, minh họa cách tổ chức dữ liệu phức tạp và thực hiện các thao tác xử lý cơ bản.
1. Quản Lý Thông Tin Sách
Ví dụ đầu tiên trình bày cách định nghĩa một cấu trúc để lưu trữ thông tin chi tiết về sách, bao gồm mã ISBN, tên sách, tác giả, giá bán và số lượng bán. Chương trình sẽ khởi tạo một danh sách các cuốn sách, sau đó sắp xếp chúng theo số lượng bán giảm dần và tính tổng doanh thu.
Định nghĩa cấu trúc và hàm
Chúng ta định nghĩa cấu trúc Sach (Book) và các nguyên mẫu hàm cho việc hiển thị, sắp xếp và tính toán doanh thu.
#include <stdio.h>
#include <string.h> // Cần cho strlen nếu muốn dùng
#define MAX_SACH 10
// Định nghĩa cấu trúc cho thông tin sách
typedef struct {
char ma_isbn[20]; // Mã ISBN của sách
char ten_sach[80]; // Tên đầy đủ của sách
char tac_gia[80]; // Tên tác giả
double gia_ban; // Giá bán lẻ
int so_luong_ban; // Số lượng sách đã bán
} Sach;
// Nguyên mẫu các hàm
void hien_thi_sach(const Sach danh_sach_sach[], int so_luong);
void sap_xep_sach_theo_doanh_so(Sach danh_sach_sach[], int so_luong);
double tinh_tong_doanh_thu(const Sach danh_sach_sach[], int so_luong);
Hàm chính (main)
Hàm main khởi tạo một mảng các đối tượng Sach, sau đó gọi các hàm để xử lý dữ liệu.
int main() {
Sach kho_sach[MAX_SACH] = {
{"978-7-5327-6082-4", "Người gác đền tử trận", "Ronald Reng", 42.0, 51},
{"978-7-308-17047-5", "Miền đất của tự do và tình yêu", "Yun Yetui", 49.0, 30},
{"978-7-5404-9344-8", "Người London", "Craig Taylor", 68.0, 27},
{"978-7-5447-5246-6", "Vòng đời của phần mềm", "Ted Chiang", 35.0, 90},
{"978-7-5722-5475-8", "Lịch sử vi mạch", "Wang Bo", 74.9, 49},
{"978-7-5133-5750-0", "Chiến tranh console", "Blake J. Harris", 128.0, 42},
{"978-7-2011-4617-1", "Quán cà phê nơi tận cùng thế giới", "John Strelecky", 22.5, 44},
{"978-7-5133-5109-6", "Chào người ngoài hành tinh", "UK Future Publishing Group", 118.0, 42},
{"978-7-1155-0509-5", "Khởi đầu vô tận", "David Deutsch", 37.5, 55},
{"978-7-229-14156-1", "Suối nguồn", "Ayn Rand", 84.0, 59}
};
printf("--- Danh sách sách theo doanh số (giảm dần) ---\n");
sap_xep_sach_theo_doanh_so(kho_sach, MAX_SACH);
hien_thi_sach(kho_sach, MAX_SACH);
printf("\nTổng doanh thu từ tất cả các cuốn sách: %.2f VNĐ\n", tinh_tong_doanh_thu(kho_sach, MAX_SACH));
return 0;
}
Chi tiết các hàm
Các hàm thực hiện việc hiển thị thông tin, sắp xếp (sử dụng thuật toán sắp xếp chọn) và tính tổng doanh thu.
// Hàm hiển thị thông tin của các cuốn sách
void hien_thi_sach(const Sach danh_sach_sach[], int so_luong) {
int i;
printf("%-18s%-35s%-25s%-10s%-10s\n", "ISBN", "Tên sách", "Tác giả", "Giá", "Đã bán");
for(i = 0; i < so_luong; ++i) {
printf("%-18s%-35s%-25s%-10.2f%-10d\n",
danh_sach_sach[i].ma_isbn,
danh_sach_sach[i].ten_sach,
danh_sach_sach[i].tac_gia,
danh_sach_sach[i].gia_ban,
danh_sach_sach[i].so_luong_ban);
}
}
// Hàm sắp xếp các cuốn sách theo số lượng bán giảm dần (sử dụng sắp xếp chọn)
void sap_xep_sach_theo_doanh_so(Sach danh_sach_sach[], int so_luong) {
int i, j, chi_so_max;
for (i = 0; i < so_luong - 1; ++i) {
chi_so_max = i;
for (j = i + 1; j < so_luong; ++j) {
if (danh_sach_sach[j].so_luong_ban > danh_sach_sach[chi_so_max].so_luong_ban) {
chi_so_max = j;
}
}
// Hoán đổi phần tử hiện tại với phần tử có số lượng bán cao nhất
if (chi_so_max != i) {
Sach temp_sach = danh_sach_sach[i];
danh_sach_sach[i] = danh_sach_sach[chi_so_max];
danh_sach_sach[chi_so_max] = temp_sach;
}
}
}
// Hàm tính tổng doanh thu từ tất cả các cuốn sách
double tinh_tong_doanh_thu(const Sach danh_sach_sach[], int so_luong) {
double tong_doanh_thu = 0.0;
for(int i = 0; i < so_luong; ++i) {
tong_doanh_thu += danh_sach_sach[i].gia_ban * danh_sach_sach[i].so_luong_ban;
}
return tong_doanh_thu;
}
2. Thao Tác Với Ngày Tháng
Ví dụ này sử dụng cấu trúc để đại diện cho một ngày cụ thể (ngày, tháng, năm). Chương trình cung cấp các chức năng để nhập ngày, tính số thứ tự ngày trong năm và so sánh hai ngày.
Định nghĩa cấu trúc và hàm
Cấu trúc ThoiGian (Date) và các nguyên mẫu hàm cho các thao tác xử lý ngày tháng.
#include <stdio.h>
#include <stdbool.h> // Để sử dụng kiểu bool
// Định nghĩa cấu trúc cho ngày tháng
typedef struct {
int nam;
int thang;
int ngay;
} ThoiGian;
// Nguyên mẫu các hàm
void nhap_ngay(ThoiGian *ngay_ptr); // Nhập ngày vào biến ThoiGian
int dem_ngay_trong_nam(ThoiGian thong_tin_ngay); // Trả về số thứ tự ngày trong năm
int so_sanh_ngay(ThoiGian ngay1, ThoiGian ngay2); // So sánh hai ngày
bool la_nam_nhuan(int nam); // Kiểm tra năm nhuận
Hàm kiểm thử
Hai hàm kiểm thử kiem_tra_chuc_nang1 và kiem_tra_chuc_nang2 được tạo ra để minh họa việc sử dụng các hàm xử lý ngày tháng.
void kiem_tra_chuc_nang1() {
ThoiGian mot_ngay;
printf("Nhập 3 ngày theo định dạng YYYY-MM-DD:\n");
for(int i = 0; i < 3; ++i) {
nhap_ngay(&mot_ngay);
printf("%d-%02d-%02d là ngày thứ %d trong năm.\n\n",
mot_ngay.nam, mot_ngay.thang, mot_ngay.ngay,
dem_ngay_trong_nam(mot_ngay));
}
}
void kiem_tra_chuc_nang2() {
ThoiGian ngay_sinh_A, ngay_sinh_B;
int ket_qua_so_sanh;
printf("Nhập ngày sinh của người A và người B (YYYY-MM-DD), lặp lại 3 lần:\n");
for(int i = 0; i < 3; ++i) {
printf("Nhập ngày sinh A: ");
nhap_ngay(&ngay_sinh_A);
printf("Nhập ngày sinh B: ");
nhap_ngay(&ngay_sinh_B);
ket_qua_so_sanh = so_sanh_ngay(ngay_sinh_A, ngay_sinh_B);
if(ket_qua_so_sanh == 0)
printf("Người A và Người B bằng tuổi.\n\n");
else if(ket_qua_so_sanh < 0)
printf("Người A lớn hơn Người B.\n\n"); // Ngày A trước ngày B
else
printf("Người A nhỏ hơn Người B.\n\n"); // Ngày A sau ngày B
}
}
int main() {
printf("--- Kiểm thử 1: Tính số thứ tự ngày trong năm ---\n");
kiem_tra_chuc_nang1();
printf("\n--- Kiểm thử 2: So sánh tuổi giữa hai người ---\n");
kiem_tra_chuc_nang2();
return 0;
}
Chi tiết các hàm
Các hàm thực hiện việc nhập dữ liệu, kiểm tra năm nhuận, tính số thứ tự ngày trong năm và so sánh ngày.
// Hàm kiểm tra năm nhuận
bool la_nam_nhuan(int nam) {
return (nam % 4 == 0 && nam % 100 != 0) || (nam % 400 == 0);
}
// Hàm nhập ngày
void nhap_ngay(ThoiGian *ngay_ptr) {
scanf("%d-%d-%d", &(ngay_ptr->nam), &(ngay_ptr->thang), &(ngay_ptr->ngay));
}
// Hàm tính số thứ tự ngày trong năm
int dem_ngay_trong_nam(ThoiGian thong_tin_ngay) {
int ngay_trong_thang[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; // Tháng 0 không dùng
int so_ngay_da_qua = 0;
for (int m = 1; m < thong_tin_ngay.thang; ++m) {
so_ngay_da_qua += ngay_trong_thang[m];
}
so_ngay_da_qua += thong_tin_ngay.ngay;
if (la_nam_nhuan(thong_tin_ngay.nam) && thong_tin_ngay.thang > 2) {
so_ngay_da_qua++;
}
return so_ngay_da_qua;
}
// Hàm so sánh hai ngày
// Trả về -1 nếu ngay1 trước ngay2
// Trả về 1 nếu ngay1 sau ngay2
// Trả về 0 nếu ngay1 và ngay2 giống nhau
int so_sanh_ngay(ThoiGian ngay1, ThoiGian ngay2) {
if (ngay1.nam != ngay2.nam) {
return (ngay1.nam < ngay2.nam) ? -1 : 1;
}
if (ngay1.thang != ngay2.thang) {
return (ngay1.thang < ngay2.thang) ? -1 : 1;
}
if (ngay1.ngay != ngay2.ngay) {
return (ngay1.ngay < ngay2.ngay) ? -1 : 1;
}
return 0; // Hai ngày hoàn toàn giống nhau
}
3. Quản Lý Tài Khoản Người Dùng
Ví dụ này minh họa cách sử dụng kiểu liệt kê (enum) để định nghĩa các vai trò người dùng khác nhau và kết hợp chúng trong một cấu trúc để lưu trữ thông tin tài khoản. Chương trình sẽ hiển thị danh sách tài khoản, che mật khẩu bằng dấu * và hiển thị vai trò người dùng bằng chuỗi ký tự dễ đọc.
Định nghĩa cấu trúc và hàm
Định nghĩa VaiTro (Role) bằng enum và cấu trúc TaiKhoan (Account).
#include <stdio.h>
#include <string.h>
// Định nghĩa kiểu liệt kê cho vai trò người dùng
enum VaiTro {
ADMIN = 0,
SINH_VIEN = 1,
GIANG_VIEN = 2
};
// Định nghĩa cấu trúc cho tài khoản người dùng
typedef struct {
char ten_dang_nhap[20]; // Tên đăng nhập
char mat_khau[20]; // Mật khẩu
enum VaiTro kieu_tai_khoan; // Loại tài khoản (admin, sinh viên, giáo viên)
} TaiKhoan;
// Nguyên mẫu hàm hiển thị thông tin tài khoản
void hien_thi_thong_tin_tai_khoan(const TaiKhoan danh_sach_tai_khoan[], int so_luong);
Hàm chính (main)
Hàm main khởi tạo một mảng các đối tượng TaiKhoan và gọi hàm hiển thị.
int main() {
TaiKhoan danh_sach_nguoi_dung[] = {
{"A1001", "123456", SINH_VIEN},
{"A1002", "123abcdef", SINH_VIEN},
{"A1009", "xyz12121", SINH_VIEN},
{"X1009", "9213071x", ADMIN},
{"C11553", "129dfg32k", GIANG_VIEN},
{"X3005", "921kfmg917", SINH_VIEN}
};
int tong_so_tai_khoan;
tong_so_tai_khoan = sizeof(danh_sach_nguoi_dung) / sizeof(TaiKhoan);
printf("--- Danh sách tài khoản người dùng ---\n");
hien_thi_thong_tin_tai_khoan(danh_sach_nguoi_dung, tong_so_tai_khoan);
return 0;
}
Chi tiết hàm hiển thị
Hàm hien_thi_thong_tin_tai_khoan sẽ lặp qua mảng tài khoản, in tên đăng nhập, một chuỗi * có độ dài bằng mật khẩu gốc và vai trò người dùng.
// Hàm hiển thị thông tin tài khoản, che mật khẩu
void hien_thi_thong_tin_tai_khoan(const TaiKhoan danh_sach_tai_khoan[], int so_luong) {
char mat_khau_che[20];
const char *ten_vai_tro;
printf("%-20s%-20s%-15s\n", "Tên đăng nhập", "Mật khẩu", "Vai trò");
for (int i = 0; i < so_luong; ++i) {
// Xác định chuỗi tên vai trò từ enum
switch (danh_sach_tai_khoan[i].kieu_tai_khoan) {
case SINH_VIEN: ten_vai_tro = "Sinh viên"; break;
case GIANG_VIEN: ten_vai_tro = "Giảng viên"; break;
case ADMIN: ten_vai_tro = "Quản trị"; break;
default: ten_vai_tro = "Không xác định"; break;
}
// Tạo chuỗi mật khẩu bị che
int do_dai_mat_khau = strlen(danh_sach_tai_khoan[i].mat_khau);
memset(mat_khau_che, '*', do_dai_mat_khau);
mat_khau_che[do_dai_mat_khau] = '\0'; // Kết thúc chuỗi
printf("%-20s%-20s%-15s\n",
danh_sach_tai_khoan[i].ten_dang_nhap,
mat_khau_che,
ten_vai_tro);
}
}
4. Quản Lý Danh Bạ Liên Lạc
Ví dụ cuối cùng quản lý danh bạ điện thoại, sử dụng cấu trúc để lưu trữ tên, số điện thoại và một cờ báo hiệu liệu đây có phải là liên hệ khẩn cấp hay không. Chương trình cho phép người dùng chọn các liên hệ làm khẩn cấp và sau đó hiển thị danh bạ đã sắp xếp: các liên hệ khẩn cấp được ưu tiên hiển thị trước, sau đó là sắp xếp theo tên theo thứ tự bảng chữ cái.
Định nghĩa cấu trúc và hàm
Cấu trúc ThongTinLienHe (Contact) và các nguyên mẫu hàm liên quan.
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // Cho qsort nếu muốn dùng
// Định nghĩa cấu trúc cho thông tin liên hệ
typedef struct {
char full_name[20]; // Tên đầy đủ
char phone_number[12]; // Số điện thoại
int is_urgent; // Cờ báo liên hệ khẩn cấp: 1 nếu là khẩn cấp; 0 nếu không
} ThongTinLienHe;
// Nguyên mẫu các hàm
void dat_lien_he_khan_cap(ThongTinLienHe danh_sach_lien_he[], int so_luong, const char ten_can_tim[]); // Đặt liên hệ là khẩn cấp
void in_danh_sach_lien_he(const ThongTinLienHe danh_sach_lien_he[], int so_luong); // In thông tin liên hệ
void sap_xep_va_hien_thi_danh_ba(ThongTinLienHe danh_sach_lien_he[], int so_luong); // Sắp xếp và hiển thị danh bạ
Hàm chính (main)
Hàm main khởi tạo danh bạ, cho phép người dùng nhập các liên hệ khẩn cấp và sau đó hiển thị danh bạ đã được sắp xếp.
#define MAX_LIEN_HE 10
int main() {
ThongTinLienHe danh_ba[MAX_LIEN_HE] = {
{"Nguyen Van A", "0912345678", 0},
{"Tran Thi B", "0901234567", 0},
{"Le Van C", "0987654321", 0},
{"Pham Thi D", "0976543210", 0},
{"Hoang Van E", "0965432109", 0},
{"Vu Thi G", "0954321098", 0},
{"Dang Van H", "0943210987", 0},
{"Bui Thi I", "0932109876", 0},
{"Do Van J", "0921098765", 0},
{"Ngo Thi K", "0900123456", 0}
};
int so_lien_he_khan_cap;
char ten_nhap[20];
printf("--- Hiển thị danh bạ gốc ---\n");
in_danh_sach_lien_he(danh_ba, MAX_LIEN_HE);
printf("\nNhập số lượng liên hệ bạn muốn đặt làm khẩn cấp: ");
scanf("%d", &so_lien_he_khan_cap);
printf("Nhập %d tên liên hệ khẩn cấp (mỗi tên trên một dòng):\n", so_lien_he_khan_cap);
for(int i = 0; i < so_lien_he_khan_cap; ++i) {
scanf("%19s", ten_nhap); // Giới hạn kích thước để tránh tràn bộ đệm
dat_lien_he_khan_cap(danh_ba, MAX_LIEN_HE, ten_nhap);
}
printf("\n--- Danh bạ đã sắp xếp (Liên hệ khẩn cấp ưu tiên, sau đó theo tên) ---\n");
sap_xep_va_hien_thi_danh_ba(danh_ba, MAX_LIEN_HE);
return 0;
}
Chi tiết các hàm
Các hàm thực hiện việc đánh dấu liên hệ khẩn cấp, hiển thị và sắp xếp danh bạ (sử dụng thuật toán sắp xếp nổi bọt với tiêu chí kép).
// Hàm đặt một liên hệ là khẩn cấp
void dat_lien_he_khan_cap(ThongTinLienHe danh_sach_lien_he[], int so_luong, const char ten_can_tim[]) {
for(int i = 0; i < so_luong; ++i) {
if(strcmp(danh_sach_lien_he[i].full_name, ten_can_tim) == 0) {
danh_sach_lien_he[i].is_urgent = 1;
break; // Tìm thấy và đã đặt, thoát vòng lặp
}
}
}
// Hàm sắp xếp và hiển thị danh bạ
// Sắp xếp: Liên hệ khẩn cấp (is_urgent=1) lên đầu, sau đó theo tên (thứ tự bảng chữ cái)
void sap_xep_va_hien_thi_danh_ba(ThongTinLienHe danh_sach_lien_he[], int so_luong) {
// Sử dụng thuật toán sắp xếp nổi bọt với tiêu chí kép
for (int p = 0; p < so_luong - 1; ++p) {
for (int q = 0; q < so_luong - 1 - p; ++q) {
int can_hoan_doi = 0;
// Ưu tiên liên hệ khẩn cấp (1 > 0)
if (danh_sach_lien_he[q].is_urgent < danh_sach_lien_he[q+1].is_urgent) {
can_hoan_doi = 1;
} else if (danh_sach_lien_he[q].is_urgent == danh_sach_lien_he[q+1].is_urgent) {
// Nếu trạng thái khẩn cấp giống nhau, sắp xếp theo tên
if (strcmp(danh_sach_lien_he[q].full_name, danh_sach_lien_he[q+1].full_name) > 0) {
can_hoan_doi = 1;
}
}
if (can_hoan_doi) {
ThongTinLienHe temp_lien_he = danh_sach_lien_he[q];
danh_sach_lien_he[q] = danh_sach_lien_he[q+1];
danh_sach_lien_he[q+1] = temp_lien_he;
}
}
}
// Sau khi sắp xếp, in danh sách ra màn hình
in_danh_sach_lien_he(danh_sach_lien_he, so_luong);
}
// Hàm in danh sách liên hệ
void in_danh_sach_lien_he(const ThongTinLienHe danh_sach_lien_he[], int so_luong) {
for(int i = 0; i < so_luong; ++i) {
printf("%-20s%-15s", danh_sach_lien_he[i].full_name, danh_sach_lien_he[i].phone_number);
if(danh_sach_lien_he[i].is_urgent) {
printf("%5s", "(Khẩn cấp)");
}
printf("\n");
}
}