Bài 55.128 phút đọc
Thiết kế giao diện container tổng quát
Sau bài này bạn sẽ làm được
- Thiết kế bộ con trỏ hàm cho so sánh, hủy, duyệt và băm
- Đặt void *ctx vào mọi hàm gọi lại
- Áp dụng tám nguyên tắc thiết kế thư viện tốt
- Viết header tự đủ và có tiền tố tên
Trước khi viết một dòng cài đặt nào, phải quyết định giao diện. Với thư viện container thì giao diện quyết định gần như mọi thứ, và sáu kiểu con trỏ hàm ở đầu bài này là phần quan trọng nhất.
#Sáu kiểu con trỏ hàm
cds.h, phần đầu
#ifndef CDS_H
#define CDS_H
#include <stddef.h>
/* So sanh hai phan tu. Tra ve am, khong, duong nhu strcmp. */
typedef int (*CmpFn) (const void *a, const void *b, void *ctx);
/* Giai phong tai nguyen ben trong mot phan tu.
KHONG giai phong chinh phan tu: container lam viec do. */
typedef void (*FreeFn)(void *pt, void *ctx);
/* Ap dung len tung phan tu khi duyet. */
typedef void (*ApplyFn)(void *pt, void *ctx);
/* Vi tu: tra ve khac 0 neu phan tu thoa man. */
typedef int (*PredFn)(const void *pt, void *ctx);
/* Bam mot khoa thanh mot so. */
typedef size_t (*HashFn)(const void *khoa, void *ctx);
/* So sanh bang nhau, cho bang bam. Tra ve khac 0 neu bang. */
typedef int (*EqFn) (const void *a, const void *b, void *ctx);| Kiểu | Dùng ở đâu | Trả về gì |
|---|---|---|
| CmpFn | vec_sort, tree_insert, heap_push | Âm, không, dương |
| FreeFn | vec_free, list_free, hm_free | Không có |
| ApplyFn | vec_foreach, tree_inorder, hm_foreach | Không có |
| PredFn | vec_find, vec_remove_if | Khác 0 nếu thỏa mãn |
| HashFn | hm_new | Một số bất kỳ |
| EqFn | hm_new | Khác 0 nếu bằng |
#Vì sao mọi hàm gọi lại cần void *ctx
Không có ngữ cảnh
/* Khong co ngu canh: phai dung bien toan cuc */
static const double *g_khoa; /* bien toan cuc */
static int ss_theo_khoa(const void *a, const void *b) {
double x = g_khoa[*(const int *)a];
double y = g_khoa[*(const int *)b];
return (x > y) - (x < y);
}
g_khoa = khoa;
qsort(idx, n, sizeof idx[0], ss_theo_khoa);
/* Ba van de:
1. khong an toan da luong
2. hai lan sap xep long nhau la hong
3. ham khong tai su dung duoc o cho khac */Có ngữ cảnh
/* Co ngu canh: moi thu di kem loi goi */
static int ss_theo_khoa(const void *a, const void *b, void *ctx) {
const double *khoa = ctx;
double x = khoa[*(const int *)a];
double y = khoa[*(const int *)b];
return (x > y) - (x < y);
}
vec_sort(v, ss_theo_khoa, khoa);
/* An toan da luong, long nhau duoc, va ham thuan tuy. */#Header cds.h
cds.h, phần giao diện
/* ===== VECTOR ===== */
typedef struct Vector Vector;
Vector *vec_new(size_t esz);
void vec_free(Vector *v, FreeFn f, void *ctx);
int vec_push(Vector *v, const void *pt);
int vec_pop(Vector *v, void *ra);
void *vec_at(const Vector *v, size_t i);
size_t vec_size(const Vector *v);
int vec_insert(Vector *v, size_t i, const void *pt);
int vec_remove(Vector *v, size_t i, void *ra);
void vec_sort(Vector *v, CmpFn cmp, void *ctx);
long vec_find(const Vector *v, PredFn p, void *ctx);
void vec_foreach(Vector *v, ApplyFn f, void *ctx);
int vec_reserve(Vector *v, size_t n);
/* ===== LIST, lien ket doi ===== */
typedef struct List List;
List *list_new(size_t esz);
void list_free(List *l, FreeFn f, void *ctx);
int list_push_front(List *l, const void *pt);
int list_push_back(List *l, const void *pt);
int list_pop_front(List *l, void *ra);
int list_pop_back(List *l, void *ra);
size_t list_size(const List *l);
void list_foreach(List *l, ApplyFn f, void *ctx);
/* ===== HEAP, hang doi uu tien ===== */
typedef struct Heap Heap;
Heap *heap_new(size_t esz, CmpFn cmp, void *ctx);
void heap_free(Heap *h, FreeFn f, void *ctx);
int heap_push(Heap *h, const void *pt);
int heap_pop(Heap *h, void *ra);
void *heap_peek(const Heap *h);
size_t heap_size(const Heap *h);
/* ===== TREE, cay tim kiem nhi phan ===== */
typedef struct Tree Tree;
Tree *tree_new(size_t esz, CmpFn cmp, void *ctx);
void tree_free(Tree *t, FreeFn f, void *ctx);
int tree_insert(Tree *t, const void *pt);
void *tree_find(const Tree *t, const void *khoa);
int tree_remove(Tree *t, const void *khoa, void *ra);
void tree_inorder(const Tree *t, ApplyFn f, void *ctx);
size_t tree_size(const Tree *t);
/* ===== HASHMAP ===== */
typedef struct HashMap HashMap;
HashMap *hm_new(size_t ksz, size_t vsz, HashFn h, EqFn eq, void *ctx);
void hm_free(HashMap *m, FreeFn fk, FreeFn fv, void *ctx);
int hm_put(HashMap *m, const void *khoa, const void *gt);
void *hm_get(const HashMap *m, const void *khoa);
int hm_remove(HashMap *m, const void *khoa, void *ra);
size_t hm_size(const HashMap *m);
void hm_foreach(HashMap *m, ApplyFn f, void *ctx);
#endif /* CDS_H */#Tám nguyên tắc thiết kế thư viện
| Nguyên tắc | Bài đã bàn | |
|---|---|---|
| 1 | Kiểu mờ, người dùng không thấy nội tại | 39.8 |
| 2 | Mọi hàm gọi lại có void *ctx | 29.6, 55.1 |
| 3 | Quyền sở hữu ghi rõ trong tài liệu | 52.1 |
| 4 | Mã lỗi nhất quán: 0 là được, âm là lỗi | 18.2 |
| 5 | Không exit trong thư viện | 32.4 |
| 6 | Không printf trong thư viện | 55.1 |
| 7 | Tiền tố tên cho mọi ký hiệu công khai | 39.6 |
| 8 | Header tự đủ, include mọi thứ nó cần | 20.2 |
Nguyên tắc 8: header tự đủ
/* cds.h */
#ifndef CDS_H
#define CDS_H
#include <stddef.h> /* cho size_t, PHAI co */
/* KHONG include <stdio.h> hay <stdlib.h> neu header khong dung toi.
Nguoi dung khong nen bi keo theo nhung thu ho khong can. */
typedef struct Vector Vector;
...
#endif
/* Kiem tra tu du bang mot dong: */
$ echo '#include "cds.h"' | gcc -Iinclude -xc -c - -o /dev/null
/* Neu no dich duoc mot minh thi header tu du.
Dua dong nay vao he thong dung, cho MOI header cong khai.
Bai 20.2 da noi. */#Quy ước mã lỗi
Ba cách, và cách nào cho việc gì
/* Cach 1: int, 0 la duoc, am la loi. Cho ham CO the that bai. */
int vec_push(Vector *v, const void *pt);
if (vec_push(v, &x) != 0) return -1;
/* Cach 2: con tro, NULL la loi. Cho ham TAO ra thu gi do. */
Vector *vec_new(size_t esz);
void *vec_at(const Vector *v, size_t i);
Vector *v = vec_new(sizeof(int));
if (v == NULL) return -1;
/* Cach 3: khong tra ve gi. Cho ham KHONG THE that bai. */
void vec_free(Vector *v, FreeFn f, void *ctx);
size_t vec_size(const Vector *v);
/* Va mot cach thu tu, cho ham tra ve chi so: */
long vec_find(const Vector *v, PredFn p, void *ctx);
/* tra ve chi so, hoac -1 neu khong tim thay.
Dung "long" chu khong "size_t" de co cho cho -1. */Một enum mã lỗi, nếu cần chi tiết hơn
/* Voi X Macro cua Bai 54.3: */
#define CDS_CAC_LOI(X) \
X(CDS_OK, 0, "Thanh cong") \
X(CDS_LOI_THAMSO, -1, "Tham so khong hop le") \
X(CDS_LOI_BONHO, -2, "Het bo nho") \
X(CDS_LOI_RONG, -3, "Container rong") \
X(CDS_LOI_BIEN, -4, "Chi so ngoai bien") \
X(CDS_LOI_TRUNG, -5, "Khoa da ton tai")
typedef enum {
#define X(ten, ma, mota) ten = ma,
CDS_CAC_LOI(X)
#undef X
} CdsLoi;
const char *cds_loi_mo_ta(CdsLoi e);
/* Uu diem so voi chi tra ve -1:
- nguoi dung phan biet duoc "het bo nho" voi "tham so sai"
- thong bao loi cho nguoi cuoi ro rang hon
Nhuoc diem: moi lan them mot ma loi la mot thay doi API.
Voi thu vien nho thi -1 la du. Voi thu vien lon thi nen co enum. */Tự làm thử
- Viết sáu kiểu con trỏ hàm và giải thích vì sao mỗi cái có
void *ctx. - Viết một hàm so sánh dùng ngữ cảnh để sắp xếp chỉ số theo một mảng khóa ngoài.
- Kiểm tra header của bạn tự đủ bằng một dòng lệnh.
- Tìm trong một thư viện mã nguồn mở xem nó phá nguyên tắc nào trong tám nguyên tắc.
- Viết
vec_pushsao cho vector không đổi gì khi thất bại. - Dùng X Macro để sinh enum mã lỗi kèm bảng mô tả.
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
- Sáu kiểu con trỏ hàm, và cả sáu đều có
void *ctxở cuối để không ai phải dùng biến toàn cục. FreeFngiải phóng tài nguyên bên trong phần tử, không giải phóng chính phần tử: container sở hữu khối.vec_attrả về con trỏ vào trong và con trỏ đó treo sau khi vector thay đổi;vec_popsao chép ra bộ đệm người dùng cấp.- Không
exitvà khôngprintftrong thư viện: trả mã lỗi và để người gọi quyết định. - Trạng thái phải không đổi khi thất bại, và mẫu để đạt được điều đó là chỉ cập nhật sau khi mọi bước đã thành công.