C 語言缺乏反射與泛型,但通過「結構體↔表結構映射」+「宏包裝 SQL」+「回調函數綁定」,可構建輕量級 ORM 風格接口。本文展示一套基於 SQLite 的完整實現:插入/刪除/更新統一入口、查詢自動映射到結構體鏈表、表名與綁定函數註冊機制。
INSERT_INTO_(table, cols, vals) 等宏生成參數化 SQL。sql_exec 處理增刪改,select_table 處理查詢映射。void* (*row_mapper)(sqlite3_stmt*) 將行轉結構體。// 業務實體
typedef struct User {
int id;
char *name;
char *password;
} User;
// 查詢結果鏈表節點
typedef struct SelectNode {
void *value; // 指向具體結構體 (User*)
struct SelectNode *next;
} SelectNode;
// 釋放回調
typedef void (*ValueFreeFunc)(void *value);
int sql_exec(const char *fmt, ...) {
char sql[1024];
va_list ap; va_start(ap, fmt);
vsnprintf(sql, sizeof(sql), fmt, ap); va_end(ap);
char *err = NULL;
int rc = sqlite3_exec(g_db, sql, NULL, NULL, &err);
if (rc != SQLITE_OK) {
fprintf(stderr, "SQL錯誤: %s\nSQL: %s\n", err, sql);
sqlite3_free(err);
return SQLITE_ERROR;
}
return SQLITE_OK;
}
// 宏定義 (## 為連接符)
#define SELECT_(cols, table, where) "SELECT " #cols " FROM " #table " WHERE " #where
#define INSERT_INTO_(table, cols, vals) "INSERT INTO " #table " (" #cols ") VALUES (" #vals ")"
#define UPDATE_(table, set, where) "UPDATE " #table " SET " #set " WHERE " #where
#define DELETE_FROM_(table, where) "DELETE FROM " #table " WHERE " #where
// 使用示例
User u = {0, "alice", "secret"};
sql_exec(INSERT_INTO_(User, id,name,password, "%d,'%s','%s'"), u.id, u.name, u.password);
// 註冊表名→映射函數 (簡化版用 strcmp,生產建議哈希表)
void *map_user_row(sqlite3_stmt *stmt) {
User *u = calloc(1, sizeof(User));
int cols = sqlite3_column_count(stmt);
for (int i = 0; i < cols; ++i) {
const char *col = sqlite3_column_name(stmt, i);
if (strcmp(col, "id") == 0) u->id = sqlite3_column_int(stmt, i);
else if (strcmp(col, "name") == 0) u->name = strdup((char*)sqlite3_column_text(stmt, i));
else if (strcmp(col, "password") == 0) u->password = strdup((char*)sqlite3_column_text(stmt, i));
}
return u;
}
int select_table(void **out_head, const char *fmt, ...) {
char sql[1024];
va_list ap; va_start(ap, fmt);
vsnprintf(sql, sizeof(sql), fmt, ap); va_end(ap);
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
SelectNode *head = NULL, *tail = NULL;
int count = 0;
while (sqlite3_step(stmt) == SQLITE_ROW) {
// 獲取表名決定映射函數
const char *tbl = sqlite3_column_table_name(stmt, 0);
void *obj = NULL;
if (tbl && strcmp(tbl, "User") == 0) obj = map_user_row(stmt);
// else if ... 其他表
if (obj) {
SelectNode *node = malloc(sizeof(SelectNode));
node->value = obj; node->next = NULL;
if (!head) head = tail = node;
else { tail->next = node; tail = node; }
count++;
}
}
sqlite3_finalize(stmt);
*out_head = head;
return count;
}
// 使用
SelectNode *list = NULL;
select_table((void**)&list, SELECT_(id, User, "name='alice' AND password='secret'"));
for (SelectNode *n = list; n; n = n->next) {
User *u = n->value;
printf("%d %s %s\n", u->id, u->name, u->password);
}
// 釋放
for (SelectNode *n = list, *next; n; n = next) {
next = n->next;
free(n->value->name); free(n->value->password); free(n->value); free(n);
}
// COUNT(*) 等無表名列,直接取整數 int total = 0; select_table((void**)&total, SELECT_(count(*), User, "1=1")); // select_table 內部識別無表名時,按整數列處理並返回給 int*
typedef void* (*RowMapper)(sqlite3_stmt*);
typedef struct { char *table; RowMapper mapper; } MapperEntry;
MapperEntry g_mappers[] = {
{"User", map_user_row},
{"Order", map_order_row},
{NULL, NULL}
};
RowMapper get_mapper(const char *table) {
for (int i = 0; g_mappers[i].table; ++i)
if (strcmp(g_mappers[i].table, table) == 0) return g_mappers[i].mapper;
return NULL;
}
| 優點 | 局限 | 演進 |
|---|---|---|
| 零依賴、極輕量、適合嵌入式 | 無遷移工具、Schema 變更需手動同步結構體 | 引入代碼生成器 (Python 解析 .h 生成映射函數) |
| SQL 完全可控、性能可預測 | 複雜 JOIN/子查詢映射困難 | 支援 View 結構體、嵌套映射回調 |
| 編譯期類型檢查 (宏參數) | 宏參數易誤用、調試困難 | C11 _Generic 封裝類型安全包裝器 |
這套方案證明了:C 語言雖無反射,但通過「約定優於配置」+「宏元編程」+「函數指針多態」,完全能構建工程可用的數據訪問層。關鍵在於建立並堅守「結構體字段 ≡ 表列」的映射契約。