Bài 29.326 phút đọc
Bảng điều phối
Sau bài này bạn sẽ làm được
- Cài bảng lệnh bằng mảng struct chứa con trỏ hàm
- Dùng bộ khởi tạo có chỉ định để bảng không lệch với enum
- So sánh bảng điều phối với switch trên bốn tiêu chí
- Kiểm tra bảng đầy đủ bằng Static_assert
Một switch hai mươi nhánh là chỗ mà thêm một chức năng luôn có nghĩa là sửa một hàm đã có. Bảng điều phối đảo ngược điều đó: thêm chức năng là thêm một dòng dữ liệu, không sửa hàm nào.
#Từ switch tới bảng
Chuỗi if, và danh sách lặp lại
int thuc_thi(const char *ten, int argc, char **argv) {
if (strcmp(ten, "help") == 0) return cmd_help(argc, argv);
else if (strcmp(ten, "add") == 0) return cmd_add(argc, argv);
else if (strcmp(ten, "del") == 0) return cmd_del(argc, argv);
else if (strcmp(ten, "list") == 0) return cmd_list(argc, argv);
else if (strcmp(ten, "quit") == 0) return cmd_quit(argc, argv);
fprintf(stderr, "Lenh khong ton tai: %s\n", ten);
return -1;
}
/* Va o mot cho khac, mot ham nua liet ke lai het: */
void in_tro_giup(void) {
puts(" help Hien thi tro giup");
puts(" add Them ban ghi");
puts(" del Xoa ban ghi");
puts(" list Liet ke");
puts(" quit Thoat");
}Một bảng dữ liệu
typedef struct {
const char *ten;
int (*ham)(int argc, char **argv);
const char *mo_ta;
} Lenh;
static const Lenh BANG_LENH[] = {
{ "help", cmd_help, "Hien thi tro giup" },
{ "add", cmd_add, "Them ban ghi" },
{ "del", cmd_del, "Xoa ban ghi" },
{ "list", cmd_list, "Liet ke" },
{ "quit", cmd_quit, "Thoat" },
};
#define SO_LENH (sizeof BANG_LENH / sizeof BANG_LENH[0])
int thuc_thi(const char *ten, int argc, char **argv) {
for (size_t i = 0; i < SO_LENH; ++i)
if (strcmp(BANG_LENH[i].ten, ten) == 0)
return BANG_LENH[i].ham(argc, argv);
fprintf(stderr, "Lenh khong ton tai: %s\n", ten);
return -1;
}
void in_tro_giup(void) {
for (size_t i = 0; i < SO_LENH; ++i)
printf(" %-6s %s\n", BANG_LENH[i].ten, BANG_LENH[i].mo_ta);
}#Bảng lệnh tra theo tên
lenh.h
#ifndef LENH_H
#define LENH_H
typedef int (*HamLenh)(int argc, char **argv);
typedef struct {
const char *ten;
HamLenh ham;
const char *mo_ta;
const char *cach_dung;
int so_doi_so_toi_thieu;
} Lenh;
int lenh_thuc_thi(const char *ten, int argc, char **argv);
void lenh_in_tro_giup(void);
#endif /* LENH_H */lenh.c
#include <stdio.h>
#include <string.h>
#include "lenh.h"
static int cmd_help(int argc, char **argv);
static int cmd_add (int argc, char **argv);
static int cmd_del (int argc, char **argv);
static int cmd_list(int argc, char **argv);
static const Lenh BANG[] = {
{ "help", cmd_help, "Hien thi tro giup", "help [lenh]", 1 },
{ "add", cmd_add, "Them ban ghi", "add <ten> <tuoi>", 3 },
{ "del", cmd_del, "Xoa ban ghi", "del <ma>", 2 },
{ "list", cmd_list, "Liet ke", "list [--sap]", 1 },
};
#define SO_LENH (sizeof BANG / sizeof BANG[0])
static const Lenh *tim(const char *ten) {
for (size_t i = 0; i < SO_LENH; ++i)
if (strcmp(BANG[i].ten, ten) == 0) return &BANG[i];
return NULL;
}
int lenh_thuc_thi(const char *ten, int argc, char **argv) {
const Lenh *l = tim(ten);
if (!l) {
fprintf(stderr, "Lenh khong biet: %s\n", ten);
fprintf(stderr, "Go 'help' de xem danh sach.\n");
return -1;
}
if (argc < l->so_doi_so_toi_thieu) {
fprintf(stderr, "Cach dung: %s\n", l->cach_dung);
return -1;
}
return l->ham(argc, argv);
}
void lenh_in_tro_giup(void) {
for (size_t i = 0; i < SO_LENH; ++i)
printf(" %-6s %-22s %s\n",
BANG[i].ten, BANG[i].cach_dung, BANG[i].mo_ta);
}
static int cmd_help(int argc, char **argv) {
if (argc >= 2) {
const Lenh *l = tim(argv[1]);
if (!l) { fprintf(stderr, "Khong co lenh %s\n", argv[1]); return -1; }
printf("%s\n%s\n", l->cach_dung, l->mo_ta);
return 0;
}
lenh_in_tro_giup();
return 0;
}terminal
./ql help
help help [lenh] Hien thi tro giup add add <ten> <tuoi> Them ban ghi del del <ma> Xoa ban ghi list list [--sap] Liet ke
./ql add
Cach dung: add <ten> <tuoi>
./ql xyz
Lenh khong biet: xyz Go 'help' de xem danh sach.
#Bảng tra theo chỉ số
typedef enum {
OP_CONG, OP_TRU, OP_NHAN, OP_CHIA,
OP_SO_LUONG /* luon de cuoi */
} MaPhepToan;
typedef int (*PhepToan)(int, int);
static int p_cong(int a, int b) { return a + b; }
static int p_tru (int a, int b) { return a - b; }
static int p_nhan(int a, int b) { return a * b; }
static int p_chia(int a, int b) { return b ? a / b : 0; }
/* Bo khoi tao CO CHI DINH: thu tu trong mang khong phu thuoc
thu tu ban viet, va them mot phep toan khong lam lech bang */
static const PhepToan BANG[OP_SO_LUONG] = {
[OP_CONG] = p_cong,
[OP_TRU] = p_tru,
[OP_NHAN] = p_nhan,
[OP_CHIA] = p_chia,
};
int tinh(MaPhepToan op, int a, int b) {
if ((unsigned)op >= OP_SO_LUONG || !BANG[op]) return 0;
return BANG[op](a, b);
}Khi mã không liên tục
/* Ma trang thai HTTP: 200, 301, 404, 500 ... khong lien tuc.
Mot mang 501 phan tu de dung 4 cho la lang phi. */
typedef struct { int ma; const char *mo_ta; } MucHTTP;
static const MucHTTP BANG_HTTP[] = {
{ 200, "OK" },
{ 301, "Moved Permanently" },
{ 400, "Bad Request" },
{ 404, "Not Found" },
{ 500, "Internal Server Error" },
};
/* Bang da sap theo ma, nen tim nhi phan duoc */
static int ss_ma(const void *k, const void *m) {
int a = *(const int *)k, b = ((const MucHTTP *)m)->ma;
return (a > b) - (a < b);
}
const char *http_mo_ta(int ma) {
const MucHTTP *m = bsearch(&ma, BANG_HTTP,
sizeof BANG_HTTP / sizeof BANG_HTTP[0],
sizeof BANG_HTTP[0], ss_ma);
return m ? m->mo_ta : "Unknown";
}#Bảo đảm bảng đầy đủ
#include <assert.h>
static const PhepToan BANG[OP_SO_LUONG] = {
[OP_CONG] = p_cong,
[OP_TRU] = p_tru,
[OP_NHAN] = p_nhan,
[OP_CHIA] = p_chia,
};
_Static_assert(sizeof BANG / sizeof BANG[0] == OP_SO_LUONG,
"bang phep toan khong khop so luong enum");Cách vừa có bảng vừa có kiểm lúc biên dịch
/* Dung X macro de sinh CA enum lan bang tu mot danh sach duy nhat.
Bai 19.5 da gioi thieu ky thuat nay. */
#define DANH_SACH_PHEP_TOAN \
X(OP_CONG, p_cong, "+") \
X(OP_TRU, p_tru, "-") \
X(OP_NHAN, p_nhan, "*") \
X(OP_CHIA, p_chia, "/")
/* Sinh enum */
#define X(ma, ham, ky_hieu) ma,
typedef enum { DANH_SACH_PHEP_TOAN OP_SO_LUONG } MaPhepToan;
#undef X
/* Sinh khai bao ham */
#define X(ma, ham, ky_hieu) static int ham(int, int);
DANH_SACH_PHEP_TOAN
#undef X
/* Sinh bang ham */
#define X(ma, ham, ky_hieu) [ma] = ham,
static const PhepToan BANG[OP_SO_LUONG] = { DANH_SACH_PHEP_TOAN };
#undef X
/* Sinh bang ky hieu */
#define X(ma, ham, ky_hieu) [ma] = ky_hieu,
static const char *const KY_HIEU[OP_SO_LUONG] = { DANH_SACH_PHEP_TOAN };
#undef X
/* Them mot phep toan = them MOT dong vao DANH_SACH_PHEP_TOAN.
Bon bang khong the lech nhau, vi chung sinh ra tu cung mot danh sach. */#Bảng hay switch
| Tiêu chí | switch | Bảng điều phối |
|---|---|---|
| Trình biên dịch bắt được thiếu nhánh | Có, nếu không có default và bật -Wswitch | Không, phải tự kiểm lúc chạy |
| Thêm một mục | Sửa hàm đã có | Thêm một dòng dữ liệu |
| Nhiều bảng song song, ví dụ tên và mô tả | Phải viết nhiều switch | Thêm trường vào cùng struct |
| Nội tuyến được | Có | Không, luôn là lời gọi gián tiếp |
| Đọc luồng chạy | Nhìn thấy hết trong một hàm | Phải tra bảng mới biết đi đâu |
| Thay đổi lúc chạy | Không | Được, gán lại ô trong bảng |
| Thân nhánh ngắn, ví dụ trả về một hằng | Gọn hơn | Rườm rà, phải viết một hàm |
Tự làm thử
- Đổi một chuỗi
ifnăm nhánh thành bảng lệnh, rồi thêm lệnh thứ sáu và đếm số dòng phải sửa. - Thêm trường
so_doi_so_toi_thieuvào bảng và bỏ đoạn kiểm tra trùng lặp khỏi từng hàm lệnh. - Viết bảng theo chỉ số bằng bộ khởi tạo có chỉ định, rồi thêm một giá trị vào giữa enum và xác nhận bảng không lệch.
- Thêm
_Static_assertkiểm kích thước bảng, rồi thử xóa một dòng khỏi enum. - Viết bảng mã HTTP rời rạc và tra bằng
bsearch. - Sinh cả enum lẫn hai bảng bằng X macro, rồi chạy
gcc -E -Pđể xem chúng bung ra thành gì.
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
- Bảng điều phối biến danh sách chức năng thành dữ liệu, nên thêm một mục là thêm một dòng chứ không sửa hàm nào.
- Nhiều bảng song song, ví dụ tên và mô tả và cách dùng, nằm trong cùng một struct nên chúng không thể lệch nhau.
- Bảng theo chỉ số phải dùng bộ khởi tạo có chỉ định, nếu không thì thêm một giá trị vào giữa enum sẽ làm lệch mọi ô.
- Luôn ép sang
unsignedkhi kiểm chỉ số, để bắt cả giá trị âm lẫn giá trị quá lớn bằng một phép so sánh. - Lý do chọn bảng là dễ bảo trì, không phải tốc độ. Trình biên dịch biến
switchtrên enum liên tục thành bảng nhảy, và bảng nhảy không có lời gọi gián tiếp.