Bỏ qua điều hướng, tới nội dung chính
Học C
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ểuDùng ở đâuTrả về gì
CmpFnvec_sort, tree_insert, heap_pushÂm, không, dương
FreeFnvec_free, list_free, hm_freeKhông có
ApplyFnvec_foreach, tree_inorder, hm_foreachKhông có
PredFnvec_find, vec_remove_ifKhác 0 nếu thỏa mãn
HashFnhm_newMột số bất kỳ
EqFnhm_newKhá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. */

#Tám nguyên tắc thiết kế thư viện

Nguyên tắcBài đã bàn
1Kiểu mờ, người dùng không thấy nội tại39.8
2Mọi hàm gọi lại có void *ctx29.6, 55.1
3Quyền sở hữu ghi rõ trong tài liệu52.1
4Mã lỗi nhất quán: 0 là được, âm là lỗi18.2
5Không exit trong thư viện32.4
6Không printf trong thư viện55.1
7Tiền tố tên cho mọi ký hiệu công khai39.6
8Header tự đủ, include mọi thứ nó cần20.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ử

  1. Viết sáu kiểu con trỏ hàm và giải thích vì sao mỗi cái có void *ctx.
  2. 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.
  3. Kiểm tra header của bạn tự đủ bằng một dòng lệnh.
  4. 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.
  5. Viết vec_push sao cho vector không đổi gì khi thất bại.
  6. 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.
  • FreeFn giả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_at trả về con trỏ vào trong và con trỏ đó treo sau khi vector thay đổi; vec_pop sao chép ra bộ đệm người dùng cấp.
  • Không exit và không printf trong 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.