7、C23标准(上):新关键字(typeof、nullptr)、二进制整数字面量、数字分隔符
各位好,欢迎来到C23标准的上半部分。
说实话,C23这个版本让我挺兴奋的。做了这么多年C语言开发,看着它一点点进化,就像看着一个老朋友不断学习新技能。今天我们要聊的几个特性,虽然看起来都是小改动,但实际用起来,嗯,真能省不少事。
7.1 typeof:类型推导,终于来了
先说说typeof。这个关键字其实很多C程序员都盼了很久。
它的作用很简单:获取一个表达式或变量的类型。说白了,就是让编译器帮你推导类型,你不用再手写那些又长又臭的类型名。
核心语法:
typeof(表达式) 变量名 = 表达式;
举个例子,你写一个宏,想创建一个和传入参数同类型的临时变量。以前怎么办?要么用typeof的GCC扩展(非标准),要么用void*然后强转,要么干脆写死类型。现在好了,C23直接把它收进标准了。
#include <stdio.h>
#define SWAP(a, b) do { \
typeof(a) temp = a; \
a = b; \
b = temp; \
} while(0)
int main() {
int x = 10, y = 20;
SWAP(x, y);
printf("x = %d, y = %d\n", x, y); // 输出: x = 20, y = 10
double pi = 3.14, e = 2.71;
SWAP(pi, e);
printf("pi = %.2f, e = %.2f\n", pi, e); // 输出: pi = 2.71, e = 3.14
return 0;
}
我个人习惯在写泛型宏时大量使用typeof。以前用GCC扩展写这类代码,移植性是个大问题。现在好了,标准支持,跨平台编译不再提心吊胆。
小技巧: typeof还可以和const、volatile等限定符配合使用。比如typeof(const int) x = 5;,这样x就是const int类型。
7.2 nullptr:告别NULL的暧昧时代
接下来聊聊nullptr。这个关键字,C++程序员早就用上了,C语言直到C23才正式引入。
为什么需要它?因为NULL在C语言里其实是个整数0,或者(void*)0。这导致了一个很尴尬的问题:当你调用一个重载函数(虽然C没有重载,但_Generic有类似效果)或者使用可变参数时,NULL会被当作整数0,而不是指针空值。
我曾经在项目中遇到过这样一个bug:
#include <stdio.h>
#include <stddef.h>
void print_int(int x) {
printf("int: %d\n", x);
}
void print_ptr(void *p) {
printf("ptr: %p\n", p);
}
#define PRINT(x) _Generic((x), \
int: print_int, \
void*: print_ptr \
)(x)
int main() {
PRINT(0); // 调用 print_int
PRINT(NULL); // 调用 print_int —— 嗯?!
PRINT(nullptr); // C23: 调用 print_ptr —— 这才对嘛
return 0;
}
你看,NULL被当作整数0传进去了,_Generic匹配到了int分支。而nullptr的类型是nullptr_t,它可以隐式转换为任何指针类型,但不会和整数混淆。
注意: nullptr不是指针,它的类型是nullptr_t。你可以把它赋值给任何指针类型,但不能赋值给整数类型。这一点和NULL完全不同。
我个人建议,从C23开始,所有指针空值都用nullptr,别再纠结NULL和0了。代码意图更清晰,也避免了一些隐晦的bug。
7.3 二进制整数字面量:0b前缀
这个特性,说实话,早该有了。很多程序员在写位运算、寄存器配置、协议解析时,经常需要直接写二进制数。以前只能用十六进制,然后心里默默换算。现在好了,直接写二进制。
#include <stdio.h>
int main() {
int a = 0b1010; // 十进制 10
int b = 0b11110000; // 十进制 240
int c = 0b10101010; // 十进制 170
printf("a = %d\n", a); // 输出: a = 10
printf("b = %d\n", b); // 输出: b = 240
printf("c = %d\n", c); // 输出: c = 170
// 位运算场景:设置第3位
int flags = 0b00000000;
flags |= 0b00001000; // 设置第3位
printf("flags = 0x%x\n", flags); // 输出: flags = 0x8
return 0;
}
我在做嵌入式开发时,经常要配置寄存器。以前写0x0F,现在直接写0b00001111,哪个位是1一目了然。你想想看,是不是比十六进制直观多了?
提示: 二进制字面量支持0b和0B两种前缀,大小写都可以。我个人习惯用小写0b,看着顺眼。
7.4 数字分隔符:让大数字更易读
最后一个特性,数字分隔符。这个改动虽小,但非常贴心。
以前写1000000,你得数半天才知道是1百万。现在可以写成1'000'000,一目了然。分隔符是单引号',可以放在数字的任何位置,不影响数值。
#include <stdio.h>
int main() {
int population = 1'000'000; // 1百万
long long national_debt = 31'000'000'000'000; // 31万亿
double pi = 3.141'592'653'589'793; // 圆周率
printf("population = %d\n", population);
printf("national_debt = %lld\n", national_debt);
printf("pi = %.15f\n", pi);
// 二进制+分隔符:完美组合
int mask = 0b1010'1111'0011'0101;
printf("mask = 0x%x\n", mask); // 输出: mask = 0xaf35
return 0;
}
数字分隔符可以和二进制字面量配合使用,效果拔群。比如配置一个16位寄存器,写成0b1010'1111'0011'0101,每4位一组,和十六进制一一对应,既直观又易读。
注意: 分隔符不能放在数字开头或结尾,也不能连续使用。比如'123、123'、1''23都是非法的。另外,分隔符不影响数值,纯粹是为了提高可读性。
知识体系总览
好了,上面我们聊了四个特性。为了让你有个整体印象,我画了一张图:
这四个特性,说白了,都是为了让代码更清晰、更安全。typeof让类型推导自动化,nullptr让指针空值不再暧昧,二进制字面量和数字分隔符则让数字表达更直观。每一个改动都不大,但组合起来,写代码的体验提升了不少。
嗯,今天就先聊到这里。这些特性在实际项目中怎么用,我后面还会结合具体场景再展开。记住,工具再好,也得用对地方才行。