C99中引入的几个新特性
写在前面的絮叨:
日子过得还是一如既往的快,离上一篇blog更新已经快两个月了。这段期间,一部分时间回家享受寒假,另一部分就是回校后开始着手毕业设计的事。家里慢悠悠的生活节奏和城市生活形成鲜明对比,在城市里久了,反而感觉家里的生活特别没有意义,甚至有种虚度青春的恐慌感。但其实大可不必,一个人生活节奏的快慢并不能决定什么,关键是不断摸索适合自己的节奏。生活本来就是多元和丰富多彩的。
----------------------------------- 正文开始的分割线 ----------------------------------
今天要与大家分享一些最近了解到的关于C/C++的知识,都是些零零碎碎的知识点,“卑之无甚高论,所言皆为常识”,希望对使用C/C++的人们有所帮助。
1, C 标准
在我学习C++的时候,没人跟我提及那些关于标准的事,往往是我使用什么编译器编程,那个编译器就成为了我默认的“标准”。但当我编写的程序越来越多,而且经常在不同的环境下编程,编译器之间的差异就渐渐露出了端倪。有时,在宿舍写好的程序原封不动拿到机房编译就无法通过,得到一些诡异的编译错误(在那个VC 6.0盛行的时代[1])。所以,人们很容易想到,要想一份程序在世界上的任何角落都可以编译运行,必须要求代码和编译器都遵从相同的标准。
a. ANSI C, C89, Standard C
第一份 C 标准是1989年由 ANSI (美国国家标准协会)颁布,此后由 ISO (国际标准化组织)进行少量改动并采纳为国际标准。ANSI C 是目前被支持最好的标准,当下绝大多数 C 代码都是基于该标准完成的。
b. C99
C99 是 1999 年由 ISO 颁布的 C 语言的第二份标准。该标准中引入了一些新的特性:inline 函数,一些新的数据类型(long long、复数,以及虚数类型),变长数组,可变参数宏,以及同 C++ 一样的单行注释 // 等等。当前主流的 C 语言编译器已经支持了 C99 中的绝大部分新特性,但支持 C99 所有特性的编译器仅有两款:Sun Studio 和 IBM Rational logiscope。
从这个页面上可以看到主流 C 语言编译器对 C99 的支持情况。从上文知道,gcc 并不支持 C99 的所有特性,那么 gcc 都支持哪些特性呢?参考这里
这里简单说一下变长数组和可变参数宏:
a. 变长数组
在 C99 之前,我们必须在定义数组的时刻指定数组的大小,这个“大小”必须是编译时已知的,往往就是整数常量或宏。在 C99 之后,我们可以在运行时指定数组的大小,也就是可以通过一个整数变量来指定数组的大小,而这个整数变量的数值是依赖于程序运行的,如:
int n; scanf("%d", &n); int a[n];
数组 a 将存储在栈中,所以如果 n 很大或不合法将会导致程序崩溃。
b. 可变参数宏
为宏定义添加类似于 scanf 和 printf 一样的可变参数列表。比如,我在写代码的时候经常会用下面的宏来输出调试信息:
#define dbg(fmt, ...) printf("%s:%d %s(): "fmt, __FILE__, __LINE__, __func__, ## __VA_ARGS__)
如果程序运行一切正常,不想要打印调试信息,仅需将此宏重新定义为空即可。
在上面的定义中,__FILE__ 和 __LINE__ 是 C 语言的预定义宏,分别被定义为当前文件的文件名和程序运行的当前行号。__func__是一个隐式变量,指明程序当前运行的函数名。__VA_ARGS__ 则与前面的 "..." 相呼应,对应于你传给该宏的可变参数列表。而它前面的 ## 则是一个 gcc 扩展,其目的是当你调用该宏而并不想传入任何参数时,去掉宏定义中最后的 ",",如:
如果没有 ## 而你又不想传入任何参数
dbg("Ignore me. Focus on your code.")
printf("%s:%d %s(): Ignore me. Focus on your code.", __FILE__, __LINE__, __func__,)
c. C1X
2007年 C 标准委员会开始起草的标准,从名字 1X 就可以看出,此标准最终敲定最快也要在201x年,再联想一下 C++0x 都已经 2011 了还没最终敲定,所以大家还是慢慢的等待吧。
2, Compound Literals in C99
Compound Literals (翻译成“复合文字”?求更好的翻译)是 C99 引入的新特性之一。C99 的标准文档中,用了一个小节(6.5.2.5)来解释该内容,个人认为这个新特性还是很实用的,所以在这里着重介绍一下,我们先来看一个例子:
struct Point { int x, y; }; Point p = (Point){1, 2};
是不是可以很容易的初始化结构体?Compound Literals 的本质就是定义一个匿名变量,整体的语法可以用
(type) { initializer }
来表示。
Point p = (Point) {1, 2};
Point tmpPoint; tmpPoint.x = 1, tmpPoint.y = 2; Point p = tmpPoint;
除此之外,Compound Literals 还可以和域指定符配合使用来定义匿名结构体,如:
Point p = (Point) {.x = 1, .y = 2};
最后,Compound Literals 还可以用于定义匿名数组,如:
int *p = (int[]) {1, 2, 3};
int tmpArr[] = {1, 2, 3}; int *p = tmpArr;
最后的最后,再重申一遍 Compound Literals 的本质:以 (type) {initializer} 的形式定义一个匿名对象。
注:虽然我一直在说 C 语言中的各种特性,但我最初是学 C++ 的,所以一直习惯用 g++ 而不是 gcc,所写的代码也不是完全兼容 c 语言的。所以上面的代码请用 g++ 编译,最好是 4.4+ 版本的 g++,我宿舍的机器上使用的是 g++ 3.4,对 Compound Literals 的支持并不是很好。下面的这段代码在 g++ 3.4 版本编译报错,而在 g++ 4.4 一切正常。
#include <stdio.h> int main() { int *p = (int[]) {1, 2, 3} return 0; }
[1] Visual C++ 6.0 的 C++ 编译器对标准支持不好。
参考资源:
Wikipedia 词条:
C(programming language)
C99
Language Standards Supported by GCC
C Macro Tips and Tricks
Compound Literals
PS:自从有了 Google 和 Wikipedia,原来很多模糊的概念和问题都渐渐找到了答案,在此向 Google 和 Wikipedia 致敬。