在开始以前请先阅读以下条款:
1.这里不要将C和C++混为一谈了,这两个语言的制定都有自己独立的标准委员会。
2.并不是所有的编译器都支持C99,试验所使用的gcc也不是完全支持C99。
3.不要以讹传讹,想要知道结果,与其将信将疑的听别人讲,不如自己动手试下。
4.代码中的一些细节之处不要忽略。
5.本文所有内容都经过了仔细揣摩实践,但本人才疏学浅,难免有错误之处,若您发现,请跟帖或是邮件指出,谢谢。
备注:
代码都在win xp sp2 & mingw32 & 3.4.2下通过。
编译指令:gcc -std=c99 source.c -o desc
作者:harite
邮箱:Harite.K(at)gmail.com
引自:http://hi.baidu.com/harite/blog/item/73aac9ceb96b6a0292457e8a.html
1.灵活数组域
struct name
{
int namelen;
char namestr[1];
};
型如这样的结构体,利用一些内存分配技巧可以使namestr数组用起来好像有多个元素。这在以前是被标准称为“没有遵守要求”的代码,虽然他们在很多实现上都工作正常(偶尔有数组越界警告)。而在C99中,添加了“灵活数组域”概念,允许结构的最后一个域省略数组大小。
struct name
{
int namelen;
char namestr[];
};
2.指定初始值
对于一个拥有很多域的结构体
struct moreelement
{
int a;
int b;
int c;
};
来说,如果我只想初始化它的几个域,则可以这样:
struct moreelement mystruct = {.a=10,. c=40};
printf("%d,%d,%d\n", test.a,test.b,test.c);
--
10,0,40
另外要说的是,同样的方式也适合union
typedef union
{
int a;
char b;
}myunion;
myunion tu = {.b='A'};
printf("%d,%c\n", tu.a,tu.b);
--
65,A
3.复合常量
有型如下面的函数
struct people
{
int length;
int weight;
int age;
};
void makepeople(struct people want);
你想给它传递一个常数,则使用如下语法调用
makepeople((struct people){1.6, 0.5, 20});
当然你也可以使用“指定初始值”功能这样调用
makepeople((struct people){.length=1.6, .age=20});
4.不具有移植性的做法
交换两个整型变量的值,高手会这样写:
int a = 20;
int b = 30;
a ^= b ^= a ^= b;
其实它不具有可移植性,因为它试图在序列点之间两次修改变量 a, 而这是无定义的。
§ 什么是序列点?
§ 序列点是一个时间点(在整个表达式全部计算完毕之后或在 ||、 &&、 ? : 或逗号 运算符处, 或在函数调用之前), 此刻尘埃落定, 所有的副作用都已确保结束。
§ ANSI/ISO C 标准这样描述: 在上一个和下一个序列点之间, 一个对象所保存的值至多只能被表达式的 计算修改一次。而且前一个值只能用于决定将要保存的值。
§ 对于ansi/iso c标准的理解:
§ 它说在一个表达式中如果某个对象需要写入, 则在同一表达式中对该对象的访问应该只局限于直接用于计算将要 写入的值。这条规则有效地限制了只有能确保在修改之前才访问 变量的表达式为合法。例如 i = i+1 合法, 而 a[i] = i++ 则非法。
为了让其具有可移植性,可以这样写:
int a = 20;
int b = 30;
a ^= b;
b ^= a;
a ^= b;
5.数组的指定初始值
有时你可能会需要指定一些数组元素的初值,C99提供了这一功能。
int array[10] = {[0] = 0, [2] = 4, [8] = 8};
这个可以和结构的指定初始值一起用,但要注意语法:
struct mystruct
{
int a[10];
int b;
int c;
} own[] = {[0].a[3] = 0, [1].c = 20};
§注意:
§以上这段代码生成了2个mystruct结构体,并且初始化第一个结构体变量中的域中的a数组中的索引值为3的那个元素的值为0,初始化第二个结构体变量中的域中的整数c值为20。(比较生疏,可见这些新特性暂时不为人们所常用:-),当然,也可能我们的见识短。)
6.新加的宏定义和可变参宏
添加的宏定义"__func__"(小写)以及为支持可变参数宏而定义的"__VA_ARGS__"(大写)的使用(这在调试&日志记录中特别有
用),另外,使用#,##“粘贴符号”时,如果参数为空,会扩展成空串而不会出错,这个放开也有其负面影响--一些调用语法会通过编译,从而造成意想不到
的副作用(下面程序有体现)。追加的一点:可变参宏的变元也可以是变元。
#include
#define DEBUG_LOGS(fmt, ...) \
fprintf(stdout, "<时间:%s %s文件名:%s|当前函数体:%s|行数:%d>\n调试内容:\n[\n" fmt "\n]\n\n", \
__TIME__,__DATE__,__FILE__, __func__, __LINE__, ##__VA_ARGS__ );/*这里加入;号也可以编译通过,不过强烈建议不要加!*/
/*可变参宏定义的参数变元也可以是变元,就是说:可变参宏可以签套*/
#define DEBUG(debug_name, ...) \
debug_name(__VA_ARGS__)
int main()
{
DEBUG_LOGS("array=%d", 20);
/*fmt参数只能是常量,不能是变量,如下方式无法通过编译
char fmt[] = "array=%d";
DEBUG_LOGS(fmt, 20);
*/
DEBUG_LOGS("array=%d,%d,%d,%d", 20, 30, 40,50);
/*
以下3种使用方式可以通过编译,但是他们是错误的使用方式,应该绝对避免!
*/
DEBUG_LOGS("array=%d");/*仍能通过编译,这里会输出上一次输出结果的第一位:20,当然,不同平台可能有差异(未验证)*/
DEBUG_LOGS("array=%d,%d");//同上,将输出20,30
DEBUG_LOGS("array=%d,%s,%ld");//可以通过编译,但是程序会异常,不过仍能正常结束(奇怪)。
/*
如果在宏DEBUG_LOGS的定义中不使用##,则以上3种调用都无法通过编译(坏##,呵呵)。
*/
/*可变参宏定义的参数变元也可以是变元,注意理解它的扩展过程*/
DEBUG(DEBUG_LOGS, "array=%d,%d", 80, 90);
return 0;
}
可见,C99中的宏功能被大大加强了!
7.对于负数的/和%操作看法的误区
之前有人说C99标准里更改了负数的/和%操作的意义,以至于以下表达式才通过:
assert(-22 / 7 == -4);
assert(-22 % 7 == 6);
而以下表达式是不通过的:
assert(-22 / 7 == -3);
assert(-22 % 7 == -1);
其实这种看法是错误的!
C99中对于这块儿的规定如下:
“6 When integers are divided, the result of the /
operator is the algebraic quotient with any fractional part discarded.
If the quotient a/b is representable, the expression (a/b)*b + a%b shall equal a”
可见,刚才那两种结果都符合C99要求,并且C99并没有对这一运算的结果做其他说明,这个结果完全是交给编译器制作者的,但是结果必须符合那个式子!
在现实中,不论是本试验所用的编译器还是vc6或是vc2005所带的c编译器都会以以下表达式为准:
assert(-22 / 7 == -3);
assert(-22 % 7 == -1);
这一结果恐怕是绝对的定局,不会改变。
(--未完待续--最后更新日期:2007-05-16-19:07--)
----其他相关资源----
用 C99 进行开放源代码的开发 - by IBM
转自:http://hi.baidu.com/harite/blog/item/73aac9ceb96b6a0292457e8a.html
阅读(903) | 评论(0) | 转发(0) |