第十八章:类型泛型编程
级别 3 -- 经验
_Generic、类型推断和匿名函数——在 C 中编写跨类型工作的代码,以及 Auto 的泛型系统如何使其自然化。
C 被设计为类型化语言:每个对象都有类型,操作针对特定类型定义。但实际程序需要不管数据的精确类型都能操作。C 已经演化出类型泛型编程的机制——_Generic、auto、typeof——Auto 通过完整的泛型系统进一步推进了这些想法。
18.1 固有的类型泛型特性
C 一直通过转换和提升具有隐式类型泛型特性:
c
// C 深入:隐式转换
short s = 42;
int i = s; // short 提升为 int(整数提升)
long l = i; // int 转换为 long(通常转换)
double d = l; // long 转换为 double(通常转换)
int a = 3;
double b = 4.2;
double c = a + b; // a 在加法前被转换为 double通常算术转换规则确定二元操作的公共类型:
- 对两个操作数执行整数提升。
- 如果类型不同,"较窄"的类型被转换为"较宽"的类型。
- 如果两者都是整数类型,有符号性根据标准中的复杂规则解决。
C 深入: C 中的整数提升规则经常令人惊讶。在大多数平台上,
short值在任何算术操作之前被提升为int。这意味着short a, b; auto c = a + b;产生int,而不是short。这些规则还以不明显的方式与有符号性交互:unsigned int对long取决于平台大小。
这些转换是自动的但不总是安全的:
c
// C 深入:转换陷阱
int big = 2147483647; // 32 位 int 的 INT_MAX
float f = big; // 可能丢失精度(float 有 23 位尾数)
int back = f; // 如果 f > INT_MAX,实现定义的行为
unsigned int u = -1; // 定义良好的:通过模转换得到 UINT_MAX
int signed_val = u; // 如果 u > INT_MAX,实现定义的行为Auto 的方法。 Auto 要求类型之间的显式转换。没有隐式窄化转换:
auto
// Auto:显式转换
fn main() {
let i int = 42
let d float = float(i) // 显式转换
let s int = int(d) // 显式转换
print("i:", i)
print("d:", d)
print("s:", s)
}18.2 泛型选择
C11 引入了 _Generic 用于编译时类型分发:
c
// C 深入:_Generic
#include <stdio.h>
#define print_val(x) _Generic((x), \
int: print_int, \
double: print_double, \
char*: print_string \
)(x)
void print_int(int x) { printf("%d\n", x); }
void print_double(double x) { printf("%f\n", x); }
void print_string(char *x) { printf("%s\n", x); }
int main(void) {
print_val(42); // 调用 print_int
print_val(3.14); // 调用 print_double
print_val("hello"); // 调用 print_string
return 0;
}_Generic 是编译时构造。编译器根据控制表达式的类型选择匹配的关联。它不在运行时求值表达式。
C 深入:
_Generic功能强大但冗长。每种类型需要自己的函数,映射必须手动维护。它对限定类型也有令人惊讶的行为:const int和int对_Generic是不同类型,所以你可能需要两者的条目。
<tgmath.h> 头文件使用 _Generic 提供类型泛型数学函数:
c
// C 深入:tgmath.h
#include <tgmath.h>
double d = sqrt(2.0); // 调用 sqrt(double)
float f = sqrtf(2.0f); // 调用 sqrtf(float) -- 但有 tgmath:
float g = sqrt(2.0f); // 也调用 sqrtf!类型泛型分发Auto 的方法。 Auto 计划通过 spec 约束提供泛型:
auto
// Auto:带 spec 约束的泛型(计划中)
// fn add<T>(a T, b T) T where T: Numeric {
// a + b
// }
// 目前,使用单独的函数或 comptime 分发
fn generic_add(a int, b int) int {
a + b
}
fn generic_add_f(a float, b float) float {
a + b
}