C 语言 99, 11 新政策

1. C99

已经是上个世纪的最后一版 C 语言,但由于谭浩强当年没有跟上,导致我们很多人也没跟上。

1.1. 语法级

1.1.1. 基本概念

1.1.1.1. 注释

C99 起支持 C++ 式的双斜杠单行注释。

1.1.1.2. 标识符

标识符中可使用由 \u\U 转义的 Unicode 字符。\u 后面为 4 位 16 进制数,\U 后面为 8 位 16 进制数,该 16 进制数表示一个 Unicode 码点,如:

int \u6211 = 42;

1.1.2. 预处理

1.1.2.1. 宏

1.1.2.1.1. 宏定义

#define 指令的形参列表尾部可使用变长参数列表 ...,使用 __VA_ARGS__ 访问变长参数,如:

#define print1(s, ...) printf(s, 1, __VA_ARGS__)

print1("%d %d %d\n", 2, 3); // printf("%d %d %d\n", 1, 2, 3);

使用 #__VA_ARGS__ 将变长参数放入一对双引号中,并用一个逗号与空格隔开(无论代码如何排版),如:

#define print2(a, b, ...) printf(#__VA_ARGS__, a, b)

print2(1, 2, %d,
    %d); // printf("%d, %d", 1, 2);

1.1.2.1.2. 预定义宏

__STDC_VERSION__ 的值为 199901L

新增宏 __STDC_HOSTED__,当目标环境为操作系统之下则为 1,目标环境无操作系统则为 0

1.1.3. 表达式

1.1.3.1. 字面量

新增整型字面量后缀 llLL,表示 long long 类型。新增整型字面量后缀 ullUlluLLULL,表示 unsigned long long 类型。

新增字符字面量转义符 \u\U\u 后面为 4 位 16 进制数,\U 后面为 8 位 16 进制数,该 16 进制数表示一个 Unicode 码点,如 '\u6211'

新增复合字面量用以表达数组、结构体、联合体,其形式为 (类型){初始化列表},如:

int i = (int[]){ 1, 2 }[1];

1.1.3.2. 初始化

初始化列表中的项,新增一种可用形式 指派器列表=初始化器。其中指派器列表的形式为数组的下标表达式 [常量表达式] 或结构体、联合体的成员访问表达式 .标识符

对于数组的初始化,可使用指派器列表指定下标,后续的下标会在此指定值的基础上依次加 1,如:

int ar[5] = { 1, [4] = 5, [1] = 2, 3 }; // { 1, 2, 3, 0, 5 }

按照初始化列表的顺序,后面相同下标的赋值会覆盖前面,如:

int ar[5] = { [3] = 5, 5, [1] = 2, 3, 4 }; // { 0, 2, 3, 4, 5 }

可用于嵌套数组,此时指派器列表可以是嵌套的数组下标表达式,用于指定当前嵌套深度下的数组下标,如:

int ar[3][3] = { [0] = { 1, [1] = 2, 3 }, { [1] = 5, 6 }, [2][1] = 8, 9 }; // { { 1, 2, 3 }, { 0, 5, 6 }, { 0, 8, 9 } }

对于结构体的初始化,可使用指派器列表指定成员,后续的成员会依照结构体中成员声明的顺序,后面相同成员的赋值会覆盖前面,如:

typedef struct S {
    int a, b, c, d, e;
} S;

S s = { 1, 1, .e = 5, .b = 2, 3 }; // { 1, 2, 3, 0, 5 }

可用于嵌套结构体,此时指派器列表可以是嵌套的成员访问表达式,用于指定当前嵌套深度下的成员,如:

typedef struct S {
    int a, b, c;
} S;

typedef struct R {
    S x, y, z;
} R;

R r = { .x = { 1, .b = 2, 3 }, { .b = 5, 6 }, .z.b = 8, 9 }; // { { 1, 2, 3 }, { 0, 5, 6 }, { 0, 8, 9 } }

1.1.4. 声明

声明不再限定于只能出现在块的开头。这是 C99 中谭浩强错过的最重要的一点,以致于到了二十一世纪我们许多人还遵守着这个限制。

1.1.4.1. 限定符

在函数声明中,限定符可用于数组形参的方括号内,数组形参会转化为指针形参,限定符转而修饰该指针,如:

int f(int[const]); // int f(int* const)
int g(const int[]); // int g(const int*)
1.1.4.1.1. restrict

新增限定符关键字 restrict 以修饰一个指针类型的左值,提示编译器在该指针的作用域内,如果经由该指针所访问的内存会被修改,则该内存仅经由该指针访问。根据此限定符,编译器能作出更大胆的优化。

1.1.5. 语句

1.1.5.1. for

for 语句的初始化子句可以是一个声明,该声明的作用域为整个循环体,包括循环体的条件表达式和迭代表达式,如:

for (int i = 0; i < 42; i++) {

1.1.6. 类型

1.1.6.1. 标量

新增至少 64 位的有符号整型 long long 和无符号整型 unsigned long long

新增算术类型关键字 _Bool,该类型的值只能是 01。通常使用头文件 stdbool.h 中定义的宏 booltruefalse。所有非零值的标量转化为 _Bool 类型后其值为 1,零值的标量转化为 _Bool 类型后其值为 0,注意这与转化为其他某种整型时的区别,特别是将其他某种整型用作布尔类型时,如:

#include <stdbool.h>

bool b1 = false; // _Bool b1 = 0;
bool b2 = NULL; // 0
bool b3 = 0.5; // 1
#define Bool unsigned char
Bool b4 = 0.5; // 0

1.1.6.2. 数组

新增变长数组。声明一个数组时如果长度不是一个常量,则声明为变长数组,如:

void f(int i) {
    int ar[i];

变长数组的声明不能使用初始化列表。变长数组的长度在运行时确定,内存在运行时分配,长度在其生命周期内不可变。变长数组的生命周期只能在函数块内部,不能作为全局变量、结构体和联合体的成员。

变长数组作为函数形参时,在函数原型声明中,使用 * 作为数组长度,该数组同样会转化为元素类型的指针,如:

void f(int[*]); // void f(int*)

变长组数 T[n]sizeof 操作仍是计算整个数组的长度,即 sizeof(T) * n,但是在运行时计算,因此不再是常量。

1.1.6.3. 枚举

允许枚举声明中的最后一项的后面出现逗号。

1.1.6.4. 结构体

新增结构体柔性数组成员。柔性数组成员只能作为不完整数组类型而声明在最后,如:

typedef struct S {
    char c;
    int ar[];
} S;

S *s = (S*)malloc(sizeof(S) + sizeof(int) * 3);
s->ar[2] = 42;

结构体的初始化列表、sizeof 操作符、赋值操作符会忽略柔性数组成员。包含柔性数组成员的结构体不能作为数组元素和结构体成员。

结构体位域字段可使用 _Bool 类型,宽度只能为 1,如:

struct S {
    _Bool b: 1;
};

1.1.7. 函数

新增函数域内的预定义静态局部变量 static const char __func__[],内容为当前函数名。

1.2. 标准库级

1.2.1. 类型

头文件 <stdbool.h>

定义了宏 booltruefalsetruefalse 是整型常量 10

头文件 <stdint.h>

定义了确定长度的整型 typedef int8_tint16_tint32_tint64_tintptr_tuint8_tuint16_tuint32_tuint64_tuintptr_t 等。

1.2.2. 数值计算

新增大量数值计算函数。

参考文档

2. C11

和 C++ 同年推出的新世纪第一个 C 语言新标准,谭浩强更加跟不上了。

2.1. 语法级

2.1.1. 基本概念

2.1.1.1. 对齐

新增操作符关键字 _Alignof 查询类型的对齐,返回类型为 size_t,返回值为常量,如:

typedef struct S {
    int i;
    char c;
} S;

_Alignof(S) // 4;

新增关键字 _Alignas 以声明对齐,其形式为 _Alignas(整型常量表达式或类型),对齐为整型常量表达式的值或类型的 _Alignof 的值,如:

typedef struct S {
    _Alignas(16) char s[42];
};

2.1.2. 预处理

2.1.2.1. 宏

__STDC_VERSION__ 的值为 201112L

新增宏 __STDC_UTF_16__,当 char16_t 使用 UTF-16 编码则为 1

新增宏 __STDC_UTF_32__,当 char32_t 使用 UTF-32 编码则为 1

新增宏 __STDC_ANALYZABLE__,当编译器支持可分析性则为 1

新增宏 __STDC_LIB_EXT1__,当标准库包含带边界检查的特定 API 则为 201112L

新增宏 __STDC_NO_ATOMICS__,当编译器不支持原子类型且标准库不包含原子类型 API 则为 1

新增宏 __STDC_NO_COMPLEX__,当编译器不支持复数类型且标准库不包含复数类型 API 则为 1

新增宏 __STDC_NO_VLA__,当编译器不支持变长数组则为 1

2.1.3. 表达式

2.1.3.1. 字面量

新增字符字面量前缀 u,字符类型为 char16_t,通常是 UTF-16 编码。如 u'我'(char16_t)0x6211,若字符的 Unicode 码点对应的 UTF-16 编码超出单个编码单元,则依照编译器的具体实现。

新增字符字面量前缀 U,字符类型为 char32_t,通常是 UTF-32 编码。如 U'我'(char32_t)0x00006211,若字符的 Unicode 码点对应的 UTF-32 编码超出单个编码单元(这都能超过?),则依照编译器的具体实现。

新增字符串字面量前缀 u8,字符类型为 char,UTF-8 编码。如 u"是我"(char[]){ 0xe6, 0x98, 0xaf, 0xe6, 0x88, 0x91, 0 }

新增字符串字面量前缀 u,字符类型为 char16_t,通常是 UTF-16 编码。如 u"是我"(char16_t[]){ 0x662f, 0x6211, 0 }

新增字符串字面量前缀 U,字符类型为 char32_t,通常是 UTF-32 编码。如 U"是我"(char32_t[]){ 0x0000662f, 0x00006211, 0 }

2.1.3.2. 泛型选择表达式

新增关键字 _Generic 以提供泛型选择表达式,其形式为 _Generic(控制表达式, 关联列表)。关联列表中的关联项由逗号分隔,关联项的形式为 类型名: 表达式default: 表达式,各表达式不能为逗号表达式。控制表达式会经历左值转换(去掉顶层的类型限定符、数组到指针、函数到指针)但不会求值,转换后的类型与各关联项中的类型名进行匹配,返回匹配到的关联项中的表达式,若无 default 项且无匹配项则编译出错。如:

int i = 42;
j = _Generic(i,
    char: i + 1,
    int: i + 2,
    default: i + 3
); // 44

2.1.3.3. 静态断言

新增关键字 _Static_assert 以提供静态断言,其形式为 _Static_assert(整型常量表达式, 字符串字面量),静态断言在编译时进行,当整型常量表达式的值为 0 则发生编译错误,字符串字面量会出现在错误信息中。

2.1.4. 声明

2.1.4.1. 存储期说明符

新增关键字 _Thread_local 以声明变量的存储期为新增的线程存储期。线程存储期为该线程的整个执行过程,变量在线程启动时初始化。不能用于函数声明。用于在块作用域中声明时,必须与 staticextern 存储期组合。

2.1.4.2. 原子类型

新增关键字 _Atomic 以声明变量为原子类型。

不能用于数组和函数声明,如:

typedef int pair[2];
// _Atomic pair p;

但可声明元素为原子类型的数组,如:

_Atomic int p[2];

2.1.5. 类型

2.1.5.1. 标量

新增 16 位和 32 位无符号字符类型 char16_tchar32_t,通常是 typedef。

2.2. 标准库级

2.2.1. 线程

新增线程支持库

头文件 <threads.h>

新增线程、线程局部存储和同步原语操作函数。

头文件 <stdatomic.h>

新增原子操作函数。

参考文档

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容