Bài 30.524 phút đọc
_Generic của C11
Sau bài này bạn sẽ làm được
- Viết macro chọn hàm theo kiểu đối số bằng _Generic
- Nêu bốn giới hạn của _Generic so với nạp chồng thật
- Kết hợp _Generic với macro để làm giao diện tổng quát
- Biết tgmath.h được xây trên cơ chế này
C11 thêm một cách chọn biểu thức theo kiểu của đối số, ngay lúc biên dịch. Nó là thứ gần nhất với nạp chồng hàm mà C từng có, và nó là nền của tgmath.h. Nó cũng có bốn giới hạn khiến nó không phải nạp chồng thật.
#Cú pháp và cách nó chạy
_Generic(bieu_thuc_dieu_khien,
kieu1: ket_qua1,
kieu2: ket_qua2,
default: ket_qua_mac_dinh)_Generic
Biểu thức chọn một trong các nhánh dựa trên kiểu của biểu thức điều khiển. Việc chọn diễn ra lúc biên dịch, và các nhánh không được chọn thậm chí không cần hợp lệ về mặt ngữ nghĩa.
gen.c
#include <math.h>
#include <stdlib.h>
#define ABS(x) _Generic((x), \
int: abs, \
long: labs, \
long long: llabs, \
float: fabsf, \
double: fabs, \
long double: fabsl, \
default: abs)(x)
#define TEN_KIEU(x) _Generic((x), \
int: "int", \
unsigned: "unsigned", \
double: "double", \
float: "float", \
char: "char", \
char *: "char *", \
const char *: "const char *", \
default: "khong biet")terminal
gcc -std=c11 -Wall -Wextra -o gen gen.c && ./gen
ABS(-5) = 5
ABS(-5.5) = 5.5
ABS(-5.5f) = 5.5
TEN_KIEU(3) = int
TEN_KIEU(3.14) = double
TEN_KIEU(3.14f) = float
TEN_KIEU('a') = int
TEN_KIEU(c) = char
TEN_KIEU("abc") = char *
TEN_KIEU(m) = char *
TEN_KIEU(q) = const char *
TEN_KIEU(i + d) = double#Bốn quy tắc về kiểu
Một: mảng suy biến thành con trỏ
char m[10];
TEN_KIEU(m) /* -> "char *", KHONG phai "char[10]" */
/* Bieu thuc dieu khien duoc chuyen doi lvalue, tuc la:
- mang suy bien thanh con tro
- ham suy bien thanh con tro ham
- bo qualifier cua kieu mang
Nen ban KHONG viet duoc nhanh cho "char[10]". */Hai: hằng ký tự có kiểu int
TEN_KIEU('a') /* -> "int" */
char c = 'a';
TEN_KIEU(c) /* -> "char" */
/* Trong C, hang ky tu 'a' co kieu int, khac han C++ noi no la char.
Bai 3.3 da nhac dieu nay. Neu macro cua ban co nhanh cho char
ma nguoi dung truyen mot hang ky tu thi ho se roi vao nhanh int. */Ba: char, signed char, unsigned char là ba kiểu khác nhau
_Generic((x),
char: "char",
signed char: "signed char",
unsigned char: "unsigned char")
/* Ba nhanh nay hop le va khong trung nhau, du tren mot he thong
cu the thi char giong het mot trong hai cai kia ve bieu dien.
Cung the voi:
int va long tren he 32 bit, dau la 4 byte nhung la hai kieu
long va long long
Neu ban chi viet nhanh cho int ma nguoi dung truyen long,
ho roi vao default hoac bi loi bien dich. */Bốn: const và volatile không bị bỏ
const char *q;
TEN_KIEU(q) /* -> "const char *", KHONG roi vao nhanh "char *" */
/* Qualifier o TANG NGOAI CUNG cua bieu thuc dieu khien thi bi bo:
const int a; TEN_KIEU(a) -> "int"
Nhung qualifier ben trong kieu con tro thi KHONG:
const char *p; -> kieu la "con tro toi const char", giu nguyen
Nen mot macro nhan chuoi phai co CA HAI nhanh: */
#define DO_DAI(x) _Generic((x), \
char *: strlen, \
const char *: strlen)(x)#Bốn giới hạn
| Giới hạn | Nạp chồng thật làm được | _Generic |
|---|---|---|
| Chọn theo SỐ đối số | Có | Không, phải thêm macro đếm đối số |
| Chọn theo kiểu TRẢ VỀ | Có, trong vài ngôn ngữ | Không, chỉ nhìn đối số |
| Chuyển đổi ngầm tới kiểu gần nhất | Có | Không, phải khớp kiểu CHÍNH XÁC |
| Áp dụng cho kiểu do người dùng thêm sau | Có | Không, danh sách nhánh cố định lúc viết macro |
#Ba ứng dụng thật
Một: in giá trị mà không cần nhớ đặc tả
#include <inttypes.h>
#include <stdio.h>
#define DAC_TA(x) _Generic((x), \
char: "%c", \
signed char: "%hhd", \
unsigned char: "%hhu", \
short: "%hd", \
unsigned short: "%hu", \
int: "%d", \
unsigned: "%u", \
long: "%ld", \
unsigned long: "%lu", \
long long: "%lld", \
unsigned long long: "%llu", \
float: "%g", \
double: "%g", \
long double: "%Lg", \
char *: "%s", \
const char *: "%s", \
void *: "%p", \
default: "%p")
#define IN(x) printf(DAC_TA(x), (x))
#define IN_DONG(x) (printf(#x " = "), IN(x), putchar('\n'))
int a = 42;
double d = 3.14;
size_t n = 100;
IN_DONG(a); /* a = 42 */
IN_DONG(d); /* d = 3.14 */
IN_DONG(n); /* n = 100 */
IN_DONG(a * 2); /* a * 2 = 84 */Hai: giao diện tổng quát cho container
/* Bai 30.2 sinh ra VecInt_day, VecDouble_day, ...
Nguoi dung phai nho ten dai. _Generic gom lai mot ten: */
#define vec_day(v, x) _Generic((v), \
VecInt *: VecInt_day, \
VecDouble *: VecDouble_day, \
VecChar *: VecChar_day)(v, x)
VecInt a;
VecDouble b;
vec_day(&a, 42); /* -> VecInt_day(&a, 42) */
vec_day(&b, 3.14); /* -> VecDouble_day(&b, 3.14) */
/* Gioi han: danh sach nhanh phai liet ke MOI kieu vector
ma du an dung, va no nam trong mot header. Nguoi dung
thu vien khong them kieu cua ho vao duoc.
Do la ly do thu vien nhu klib khong dung _Generic ma bat
nguoi dung goi ten day du. */Ba: tgmath.h của thư viện chuẩn
#include <tgmath.h>
float f = 2.0f;
double d = 2.0;
long double l = 2.0L;
sqrt(f); /* goi sqrtf */
sqrt(d); /* goi sqrt */
sqrt(l); /* goi sqrtl */
/* Truoc C99 ban phai tu nho ba ten. tgmath.h dinh nghia
sqrt la mot macro HAM chon mot trong ba: */
#define sqrt(z) __TGMATH_CPLX(z, sqrt, csqrt)
/* Vi no la macro HAM, no chi bung khi co dau ngoac ngay sau ten.
Nen dong nay VAN chay: */
double (*p)(double) = sqrt; /* sqrt khong theo sau boi (
-> macro khong bung
-> day la ham sqrt that */
printf("%f\n", p(2.0)); /* 1.414214 */Tự làm thử
- Viết macro
TEN_KIEUvà thử với hằng ký tự, biếnchar, chuỗi hằng, mảngchar, vàconst char *. - Viết
ABSbằng_Genericvà kiểm vớiint,float,double. - Viết hai nhánh cùng kiểu qua hai
typedefvà đọc thông báo lỗi. - Viết macro
IN_DONGvà dùng nó vớisize_t, xác nhận nó in đúng mà không cần bạn nhớ%zu. - Chạy
TEN_KIEU(i++)rồi ini, xác nhận nó không đổi. - Thử lấy địa chỉ của
sqrtsau khi includetgmath.hvà sau khi includemath.h, so hai kết quả.
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
_Genericchọn một nhánh theo kiểu của biểu thức điều khiển, ngay lúc biên dịch, và không tốn gì lúc chạy.- Mảng suy biến thành con trỏ, hằng ký tự có kiểu
int, vàconst char *không rơi vào nhánhchar *. - Nó khớp kiểu chính xác, không xét chuyển đổi ngầm, nên một macro đầy đủ cho số nguyên cần mười một nhánh.
- Mọi nhánh phải hợp lệ về cú pháp, nên hãy để mỗi nhánh chỉ là một tên hàm.
- Biểu thức điều khiển không được đánh giá, chỉ kiểu của nó được dùng.