Bài 14 · Vận dụng · 22 phút· Cập nhật 11/06/2026
Struct & kiểu tự đặt tên
Biên soạn bởi Nguyễn Anh Tuấn
Dùng struct để gom nhiều trường khác kiểu; truy cập bằng dấu chấm, truyền struct vào hàm, typedef và thay mảng song song bằng mảng struct.
Mảng (Bài 13) chứa nhiều giá trị - nhưng phải cùng một kiểu. Trong khi một "việc cần làm" của app Todo gồm ba thuộc tính khác kiểu: mã số (int), tên việc (chuỗi), đã xong chưa (bool - Bài 7). Cách xoay xở đầu tiên là ba mảng song song, thẳng hàng theo chỉ số. Chạy thử nhiệm vụ "xoá một việc" trên cả hai cách lưu mà xem:
Nhiệm vụ: xoá việc "hoc bai" (đang ở vị trí thứ 2) khỏi cả hai cách lưu.
| id | ten | xong |
|---|---|---|
| 1 | tuoi cay | true |
| 2 | hoc bai | false |
| 3 | rua chen | false |
| id | ten | xong |
|---|---|---|
| 1 | tuoi cay | true |
| 2 | hoc bai | false |
| 3 | rua chen | false |
- ▸Mảng song song HOẠT ĐỘNG - nhưng mọi thao tác phải lặp đúng trên từng mảng, quên một cái là dữ liệu ghép sai.
- ▸Tệ nhất: C không báo lỗi gì - chương trình chạy tiếp với dữ liệu lệch.
- ▸struct chữa tận gốc: gom các thuộc tính thành MỘT bản ghi, đi đâu đi nguyên khối.
Khai báo struct là tự đặt một kiểu mới mô tả bản ghi của bạn; mỗi biến kiểu đó chứa đủ các trường (field), truy cập bằng dấu chấm:
task.c - khởi tạo theo TÊN trường, phong cách Modern C
#include <stdio.h>
#include <stdbool.h>
struct Task {
int id;
char ten[32];
bool xong;
};
int main(void) {
struct Task t = { .id = 1, .ten = "tuoi cay", .xong = false };
t.xong = true;
printf("[%c] #%d %s\n", t.xong ? 'x' : ' ', t.id, t.ten);
return 0;
} Kết quả khi chạy
[x] #1 tuoi cay
- ▸Khai báo struct Task {…}; tạo ra kiểu mới - chưa tốn ô nhớ nào (nhớ Bài 4: đây là khai báo kiểu, biến t mới là định nghĩa).
- ▸Khởi tạo theo tên trường { .id = 1, … } (designated initializer): không sợ đảo thứ tự trường - Modern C khuyên dùng hẳn cách này.
- ▸t.xong ? 'x' : ' ' là toán tử ?: - gọn cho "nếu xong thì x, không thì để trống".
Đưa struct vào hàm là truyền theo giá trị: hàm nhận một bản sao. Sửa bản sao thì bản gốc bình an vô sự - muốn giữ thay đổi, hãy trả struct về và gán lại:
diem.c - sửa bản sao vô ích; trả về rồi gán mới ăn
#include <stdio.h>
struct Diem { int x, y; };
void keo_phai_hong(struct Diem d) {
d.x = d.x + 10; /* sua BAN SAO - vo ich */
}
struct Diem keo_phai(struct Diem d) {
d.x = d.x + 10;
return d; /* tra ban da sua ve */
}
int main(void) {
struct Diem a = { 3, 4 };
keo_phai_hong(a);
printf("sau keo_phai_hong: a.x = %d\n", a.x);
a = keo_phai(a);
printf("sau a = keo_phai(a): a.x = %d\n", a.x);
return 0;
} Kết quả khi chạy
sau keo_phai_hong: a.x = 3 sau a = keo_phai(a): a.x = 13
Sẽ gặp lại
Ghép hai thứ đã học: mảng CÁC struct thay trọn bộ ba mảng song song. Kèm typedef để khỏi lặp chữ struct mỗi lần khai báo:
todo.c - danh sách việc bằng MỘT mảng Task
#include <stdio.h>
#include <stdbool.h>
typedef struct {
int id;
char ten[32];
bool xong;
} Task;
int main(void) {
Task ds[3] = {
{ 1, "tuoi cay", true },
{ 2, "hoc bai", false },
{ 3, "rua chen", false }
};
int chua_xong = 0;
for (int i = 0; i < 3; i++) {
printf("[%c] #%d %s\n", ds[i].xong ? 'x' : ' ', ds[i].id, ds[i].ten);
if (!ds[i].xong) chua_xong++;
}
printf("Con %d viec chua xong\n", chua_xong);
return 0;
} Kết quả khi chạy
[x] #1 tuoi cay [ ] #2 hoc bai [ ] #3 rua chen Con 2 viec chua xong
- ▸ds[i].ten - đọc trong ra ngoài: phần tử thứ i của mảng, rồi lấy trường ten của nó.
- ▸typedef struct {…} Task; đặt bí danh: từ đây viết Task thay cho cả cụm - code gọn và dễ đọc hơn hẳn.
- ▸Xoá/thêm/sửa giờ chỉ đụng MỘT mảng - đúng điều công cụ Bước 1 đã chứng minh.
Trung thực: kích thước struct
Bạn vừa nhận công cụ đóng gói dữ liệu đầu tiên - đúng thứ mà Modern C dạy ngay từ Level 1, vì gần như mọi chương trình thật đều xoay quanh các bản ghi: học sinh, sản phẩm, giao dịch, việc cần làm…
- ▸Mảng = nhiều phần tử cùng kiểu; struct = một bản ghi nhiều trường khác kiểu; mảng struct = danh sách bản ghi.
- ▸struct gán được nguyên khối (a = b), truyền vào hàm là copy - an toàn, dễ đoán.
- ▸typedef cho kiểu một cái tên đáng đọc - gặp dày đặc trong code C ngoài đời.
Bài tiếp theo
Câu hỏi thường gặp
Mảng là NHIỀU phần tử CÙNG kiểu, truy cập bằng chỉ số ([i]). struct là MỘT bản ghi gồm các trường KHÁC kiểu, truy cập bằng tên (.id, .ten). Chúng bổ trợ nhau: thực tế hay dùng "mảng các struct" - nhiều bản ghi, mỗi bản ghi nhiều trường.
Cách theo TÊN trường (designated initializer, có từ C99 và được Modern C khuyên dùng) không phụ thuộc thứ tự khai báo trường: sau này ai đó thêm/đảo trường trong struct, code khởi tạo của bạn vẫn đúng. Cách theo vị trí thì lặng lẽ gán nhầm trường - bug khó tìm.
Có copy thật: struct 40 byte là copy 40 byte. Với struct nhỏ thì không đáng kể và đổi lại rất an toàn (hàm không phá được bản gốc). Struct lớn hoặc cần SỬA bản gốc thì truyền địa chỉ - đó là bài "Struct & con trỏ struct" của khoá C nâng cao.
Vì ten là MẢNG char nằm trong struct - mảng không gán được bằng dấu = sau khi đã khai báo (chỉ lúc khởi tạo). Muốn đổi nội dung thì chép chuỗi vào bằng hàm strcpy của thư viện <string.h>. Lưu ý thú vị: bản thân struct thì GÁN ĐƯỢC nguyên khối (a = b copy mọi trường - kể cả mảng bên trong).
Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.
Trả lời vài câu để chắc rằng em đã nắm bài.
struct giải quyết điểm yếu nào của mảng song song?
- 1
Phá rồi chữa
Trong công cụ Bước 1: cố tình xoá thiếu một mảng để gây lệch hàng, quan sát hàng dữ liệu sai; đặt lại và làm đúng đủ 3 mảng.
Hoàn thành khi: Chỉ ra được chính xác hàng nào ghép sai dữ liệu khi lệch, và vì sao C không báo lỗi.
- 2
Bản ghi đầu tiên của mèo con
Khai báo
struct MeoCon { ten[24], tuoi, can_nang }rồi tạo một biến bằng khởi tạo theo tên trường và in ra.Hoàn thành khi: Biên dịch sạch với bộ cờ của khoá; in đủ 3 trường bằng dấu chấm.
- 3
Chứng minh "truyền giá trị"
Viết hàm
sinh_nhat(struct MeoCon m)tăngm.tuoibên trong. Gọi xong in tuổi bản gốc. Rồi sửa thànhm = sinh_nhat(m)(hàm trả struct).Hoàn thành khi: Bản đầu: tuổi gốc KHÔNG đổi. Bản sau: tuổi tăng 1 - hiểu rõ copy vào, copy ra.
- 4
Sổ điểm 3 môn
Tạo
typedef struct { ten[16]; double diem; } Mon;mảng 3Mon(Toán/Văn/Tin), duyệt vòng lặp tính điểm trung bình.Hoàn thành khi: In từng môn một dòng và dòng cuối là trung bình cộng - tự kiểm lại bằng máy tính tay.
- 5
Gán nguyên khối
Tạo
Task a, rồiTask b = a;. Sửab.ten(bằngstrcpy) vàb.xong. In cảalẫnb.Hoàn thành khi:
agiữ nguyên,bmang giá trị mới - struct gán=là copy trọn bộ, hai bản độc lập. - 6
Nâng cấp dự án cuối khoá (làm trước một bước)
Mở bài kế (Dự án cuối khoá), lấy app Todo phiên bản mảng song song và viết lại bằng MỘT mảng
Tasknhư Bước 4.Hoàn thành khi: Mọi lệnh them/xong/in chạy như cũ nhưng code chỉ còn một mảng - không còn cảnh "sửa mảng này quên mảng kia".