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还可以和constvolatile等限定符配合使用。比如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,别再纠结NULL0了。代码意图更清晰,也避免了一些隐晦的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一目了然。你想想看,是不是比十六进制直观多了?

提示: 二进制字面量支持0b0B两种前缀,大小写都可以。我个人习惯用小写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位一组,和十六进制一一对应,既直观又易读。

注意: 分隔符不能放在数字开头或结尾,也不能连续使用。比如'123123'1''23都是非法的。另外,分隔符不影响数值,纯粹是为了提高可读性。

知识体系总览

好了,上面我们聊了四个特性。为了让你有个整体印象,我画了一张图:

C23标准(上)核心特性 C23 新特性 typeof 类型推导,泛型宏利器 nullptr 指针空值,告别NULL歧义 0b 二进制字面量 直观表达位模式 ' 数字分隔符 提升大数字可读性 四个特性共同提升代码的可读性、安全性和表达能力

这四个特性,说白了,都是为了让代码更清晰、更安全。typeof让类型推导自动化,nullptr让指针空值不再暧昧,二进制字面量和数字分隔符则让数字表达更直观。每一个改动都不大,但组合起来,写代码的体验提升了不少。

嗯,今天就先聊到这里。这些特性在实际项目中怎么用,我后面还会结合具体场景再展开。记住,工具再好,也得用对地方才行。


公众号:蓝海资料掘金营,微信deep3321