Bài 16.118 phút đọc
Enum cơ bản
Sau bài này bạn sẽ làm được
- Khai báo enum và hiểu quy tắc tự đánh số
- Biết kiểu thật mà enum được cài đặt thành
- So sánh enum với define và với const
- Nhận ra bốn giới hạn của enum trong C
Enum cho bạn đặt tên cho một tập hợp giá trị nguyên có liên quan. Nó là công cụ đơn giản nhất trong Phần 7, nhưng cũng là công cụ làm mã dễ đọc nhất, và nó mở ra một mẹo rất giá trị ở Bài 16.2.
#Cú pháp và quy tắc đánh số
enum TrangThai {
CHO, /* 0 */
DANG_CHAY, /* 1 */
TAM_DUNG, /* 2 */
KET_THUC /* 3 */
};
enum TrangThai t = DANG_CHAY;Enum
Một kiểu liệt kê, gồm một tập hằng số nguyên có tên. Giá trị mặc định bắt đầu từ 0 và tăng dần một đơn vị.
danh-so.c
/* Tự đánh số từ 0 */
enum A { X, Y, Z }; /* 0, 1, 2 */
/* Đặt giá trị đầu, phần sau tiếp tục từ đó */
enum B { P = 5, Q, R }; /* 5, 6, 7 */
/* Đặt riêng từng cái */
enum Http {
OK = 200,
NOT_FOUND = 404,
SERVER_LOI = 500,
};
/* Trộn: cái nào không ghi thì tiếp tục từ cái trước */
enum C { M = 1, N, O = 10, P2, Q2 }; /* 1, 2, 10, 11, 12 */
/* Giá trị trùng nhau hoàn toàn hợp lệ */
enum D { DAU = 0, KHONG = 0, MOT = 1 };
/* Giá trị âm cũng được */
enum E { LOI = -1, THANH_CONG = 0 };Kết hợp typedef
/* Không có typedef: phải viết chữ enum mỗi lần */
enum TrangThai t1;
/* Có typedef: gọn hơn */
typedef enum {
TT_CHO,
TT_DANG_CHAY,
TT_TAM_DUNG,
TT_KET_THUC,
} TrangThai;
TrangThai t2 = TT_DANG_CHAY;
/* Giữ cả tên thẻ, giống khuyến nghị ở Bài 15.2 */
typedef enum TrangThai {
TT_CHO,
TT_DANG_CHAY,
} TrangThai;#Kiểu thật của enum
kieu-that.c
#include <stdio.h>
typedef enum { A, B, C } E;
int main(void)
{
E e = B;
printf("sizeof(E) = %zu\n", sizeof(E));
printf("sizeof(e) = %zu\n", sizeof(e));
printf("gia tri = %d\n", e); /* in bằng %d, vì enum là số nguyên */
int i = e; /* enum chuyển sang int tự động */
e = 2; /* và ngược lại, không cần ép kiểu */
return 0;
}terminal
./kieu-that
sizeof(E) = 4 sizeof(e) = 4 gia tri = 1
| Câu hỏi | Trả lời |
|---|---|
| Enum có phải kiểu riêng không | Có, nhưng nó tương thích với một kiểu nguyên nào đó |
| Kiểu nguyên nào | Do trình biên dịch chọn, thường là int nếu mọi giá trị vừa trong int |
| sizeof bằng bao nhiêu | Thường là 4, nhưng chuẩn không bảo đảm |
| In bằng định dạng gì | %d, vì giá trị được nâng lên int khi truyền cho printf |
| Chuyển sang int | Tự động, không cần ép kiểu |
| Gán số nguyên vào biến enum | Được, kể cả số không có trong danh sách |
#So với define và const
/* Ba cách đặt tên cho một hằng số */
#define TT_CHO 0 /* macro */
static const int tt_cho = 0; /* biến const */
enum { TT_CHO = 0 }; /* hằng enum */| define | const int | enum | |
|---|---|---|---|
| Có kiểu | Không | Có | Có |
| Trình gỡ lỗi thấy tên | Không | Có | Có |
| Dùng làm kích thước mảng | Được | KHÔNG | Được |
| Dùng làm nhãn case | Được | KHÔNG | Được |
| Có phạm vi | Không, tới hết file | Có | Có |
| Chiếm bộ nhớ | Không | Có thể | Không |
| Nhóm các hằng liên quan | Không | Không | Có |
| Lấy địa chỉ được | Không | Có | Không |
Mẹo enum vô danh cho hằng số đơn lẻ
/* Không cần tên kiểu, chỉ cần hằng số */
enum { TOI_DA = 100, DAI_TEN = 64 };
char ten[DAI_TEN];
int ds[TOI_DA];
switch (x) {
case TOI_DA: break; /* dùng được làm nhãn case */
}#Bốn giới hạn của enum trong C
Giới hạn 1: không có kiểm tra phạm vi
typedef enum { DO, XANH, LUC } Mau;
Mau m = 42; /* hợp lệ, không cảnh báo */
Mau n = DO + XANH; /* hợp lệ, bằng 1, tình cờ là XANH */
++m; /* hợp lệ, enum là số nguyên */Giới hạn 2: hai enum khác nhau trộn được với nhau
typedef enum { DO, XANH } Mau;
typedef enum { NHO, LON } CoLon;
Mau m = DO;
CoLon c = NHO;
m = c; /* HỢP LỆ trong C, không cảnh báo mặc định */
if (m == LON) { } /* cũng hợp lệ, so hai kiểu khác nhau */Giới hạn 3: không có cách lấy số phần tử
Đếm tay
typedef enum { TT_CHO, TT_CHAY, TT_DUNG } TrangThai;
const char *ten[3] = { "Cho", "Chay", "Dung" };
/* Thêm một trạng thái mới thì phải nhớ sửa con số 3 ở đây,
và ở mọi chỗ khác trong chương trình. */Thêm một hằng đếm ở cuối
typedef enum {
TT_CHO,
TT_CHAY,
TT_DUNG,
TT_SO_LUONG, /* luôn là phần tử cuối, giá trị bằng số phần tử thật */
} TrangThai;
const char *ten[TT_SO_LUONG] = {
[TT_CHO] = "Cho",
[TT_CHAY] = "Chay",
[TT_DUNG] = "Dung",
};
/* Thêm TT_TAM_DUNG trước TT_SO_LUONG thì mảng tự lớn thêm một ô,
và trình biên dịch cảnh báo nếu bạn quên điền tên cho nó. */Giới hạn 4: không có cách in tên tự động
in-ten.c
typedef enum { TT_CHO, TT_CHAY, TT_DUNG, TT_SO_LUONG } TrangThai;
/* Cách 1: bảng tra, đơn giản nhất */
static const char *TEN[TT_SO_LUONG] = {
[TT_CHO] = "CHO",
[TT_CHAY] = "CHAY",
[TT_DUNG] = "DUNG",
};
const char *tt_ten(TrangThai t)
{
if (t < 0 || t >= TT_SO_LUONG) return "KHONG_XAC_DINH";
return TEN[t] != NULL ? TEN[t] : "CHUA_DAT_TEN";
}
/* Cách 2: switch, để trình biên dịch bắt lỗi thiếu nhánh, xem Bài 16.2 */
const char *tt_ten2(TrangThai t)
{
switch (t) {
case TT_CHO: return "CHO";
case TT_CHAY: return "CHAY";
case TT_DUNG: return "DUNG";
case TT_SO_LUONG: break;
}
return "KHONG_XAC_DINH";
}#Bốn mẫu hữu ích
Mẫu 1: mã lỗi có tên
typedef enum {
LOI_KHONG = 0,
LOI_THAM_SO = -1,
LOI_BO_NHO = -2,
LOI_MO_FILE = -3,
LOI_DINH_DANG = -4,
} MaLoi;
MaLoi doc_cau_hinh(const char *duong_dan);
if (doc_cau_hinh("a.cfg") != LOI_KHONG)
fprintf(stderr, "That bai\n");So với việc trả về số âm một không tên, cách này cho người gọi biết chính xác chuyện gì hỏng, và trình gỡ lỗi hiện tên chứ không hiện con số.
Mẫu 2: cờ bit
co-bit.c
typedef enum {
CO_DOC = 1 << 0, /* 1 */
CO_GHI = 1 << 1, /* 2 */
CO_CHAY = 1 << 2, /* 4 */
CO_AN = 1 << 3, /* 8 */
} Co;
unsigned co = CO_DOC | CO_GHI; /* kết hợp bằng phép hoặc */
if (co & CO_DOC) printf("doc duoc\n");
if (co & CO_CHAY) printf("chay duoc\n");
co |= CO_CHAY; /* bật thêm một cờ */
co &= ~CO_GHI; /* tắt một cờ */Mẫu 3: enum làm chỉ số bảng
typedef enum { NGON_VI, NGON_EN, NGON_JA, NGON_SO_LUONG } NgonNgu;
static const char *CHAO[NGON_SO_LUONG] = {
[NGON_VI] = "Xin chao",
[NGON_EN] = "Hello",
[NGON_JA] = "Konnichiwa",
};
const char *loi_chao(NgonNgu n)
{
return (n >= 0 && n < NGON_SO_LUONG) ? CHAO[n] : CHAO[NGON_EN];
}Mẫu 4: nhãn cho union có nhãn
typedef enum { GT_INT, GT_DOUBLE, GT_CHUOI } LoaiGiaTri;
typedef struct {
LoaiGiaTri loai;
union {
long i;
double d;
char *s;
};
} GiaTri;Đây là mẫu quan trọng nhất trong ba chương của Phần 7, và Bài 17.2 dành riêng cho nó.
Tự làm thử
- Khai báo enum theo cả bốn kiểu đánh số trong bài, in giá trị từng hằng.
- Gán một số nằm ngoài tập enum vào biến enum, in ra và giải thích vì sao trình biên dịch cho qua.
- So sánh hai enum khác kiểu, biên dịch có và không có
-Wenum-compare. - Thử dùng
static const intlàm kích thước mảng và làm nhãncase, chép lại hai thông báo lỗi. - Cài mẫu hằng đếm ở cuối enum, thêm một trạng thái mới và xác nhận bảng tên tự lớn theo.
- Cài kỹ thuật danh sách X sinh cả enum lẫn bảng tên, thêm một hằng và kiểm chứng cả hai cùng cập nhật.
- Cài mẫu cờ bit với bốn cờ, viết hàm bật, tắt và kiểm tra từng cờ.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- Enum đánh số từ 0 và tăng dần, nhưng đặt giá trị riêng cho từng hằng được, kể cả trùng nhau và âm.
- Trong C, enum chỉ là hằng số nguyên có tên. Không có kiểm tra phạm vi và hai enum khác kiểu trộn được với nhau.
- Dùng
enumthay choconst intkhi cần hằng số nguyên, vìconst intkhông phải hằng biên dịch trong C. - Thêm một hằng đếm ở cuối để biết số phần tử, và dùng nó làm kích thước bảng tra.
- Tên hằng enum nằm ở không gian tên chung, nên luôn đặt tiền tố để tránh đụng tên.