C语言有哪些鲜为人知的特性?

译注:本文摘编自 Quora 的一个热门问答贴。 请在linux系统下测试本文中出现的代码

Andrew Weimholt 的回复:

switch语句中的case 关键词可以放在if-else或者是循环当中

Brian Bi 的回复:

1. 声明紧随用途之后

理解声明有一条很简单的法则,不过不是什么“从左向右”这种没道理却到处宣传的法则。这一法则的观点是,一个声明是要告诉你,你所声明的对象要如何使用。例如:

更多详情请看这里: Brian Bi’s answer to C (programming language): Why doesn’t C use better notation for pointers?

2. 指定初始化:

在C99之前,你只能按顺序初始化一个结构体。在C99中你可以这样做:

这段代码首先初始化了foo.z,然后初始化了foo.x. foo.y 没有被初始化,所以被置为0。
这一语法同样可以被用在数组中。以下三行代码是等价的:

3. 受限指针(C99):

restrict关键词是一个限定词,可以被用在指针上。它向编译器保证,在这个指针的生命周期内,任何通过该指针访问的内存,都只能被这个指针改变。比如,在

编译器可能会假设,xy 所指的并不是同一个int对象,因为如果它们指向了同一个对象,则x的值将可以通过y修改,这正是你保证不会发生的。因此,将允许编译器来优化f,就好像函数原本被写做如下这样:

如果你违反协议向f传递两个指向同一int对象的指针时,将产生未定义行为。

我猜想,引入这一特性最初的动机之一是想让C语言在数值计算时可以Fortran一样快。在Fortran 中,默认假定数组不会重叠,因此只有你通过restrict 限定词来显式的告诉编译器数组不能重叠,编译器才能在C语言中进行这样的优化。

4. 静态数组索引(C99)

中,你向编译器保证,你传递给f 的指针指向一个具有至少10个int 类型元素的数组的首个元素。我猜这也是为了优化;例如,编译器将会假定a 非空。编译器还会在你尝试要将一个可以被静态确定为null的指针传入或是一个数组太小的时候发出警告。

你不能修改指针a.,这和说明符int * const a.作用是一样的。然而,当你结合上一段中提到的static 使用,比如在int a[static const 10] 中,你可以获得一些使用指针风格无法得到的东西。

5. 泛型表达式(C11)

这个表达式会在编译期间根据控制表达式的类型,在一个含有一个或多个备选方案的集合中做出选择。下面这个例子可以很好的说明这一切:

因此,如果exprlong double类型的, cbrt(expr) 被转换为cbrtl(expr),如果是float类型 则转换为cbrtf(expr) ,或是转换为cbrt(expr),如果是其他不同的类型(比如说double )。注意,_Generic 可以用在宏以外的地方,但是用在宏里面最好因为C不允许你进行函数重载。

6. wint_t (C99)

我相信大家都知道wint_t 但是 wint_t 到底是个什么鬼东西呢?

好吧,记住fgetc 实际上并不会返回 char 。它会返回int。显然这是因为fgetc 必须返回返回一个与其他char 都不同的值,也就是EOF,表示到达文件末尾。基于相同的原因,fgetwc 并不返回wchar_t。它会返回一个类型,叫做wint_t 可以表示所有无效wchar_t 类型,包括WEOF,来表示到达文件末尾。

Michal Forišek

下面这段C程序可以准确的打印2的747次方而不产生误差。这是为什么呢?

程序:

输出结果:

答案:

这个问题包含两个部分。
其一,2的次方可以在double 中被准确的保存而不产生任何精度上的损失(这一结论直到2^1023都是对的,再往后就会产生上溢,得到一个正无穷的值)

另外一部分,很多人猜测是语言实现中的某些特殊情况导致的,但是实际上并非如此。的确,当输入的数据可以被2的某高次方整除时,有一部分代码被执行了,但是本质上这只是通常实现工作时的一个副作用。基本上,printf 在打印数字(任何类型)的时候只是做了从二进制到十进制的转换。并且由于结果对于浮点数可能会过大,printf 的内部实现包含和使用一个大整型实现,尽管在C中并没有大整型这种变量(在gcc源代码中,vfprintf.cdtoa.c 中包含了很多转换,如果你想要了解可以一看。)

如果你尝试打印3^474,

程序:

输出结果:

结果仍然是一个很大的数且位数也正确,但是这一次却不够精确。这里会产生一个相对误差,因为3^474不能以双精度浮点数准确的表示。准确的数应该是这样的143045676882846603471

译注:在linux系统上是可以的,在windows 64位上后面会有很多0

Utkal Sinha

我发现一些C语言特性或者是小技巧,我觉得只有很少的人知道。

1. 不使用加号来使数字相加

因为printf() 函数返回它所打印的字符的个数,我们可以利用这一点来使数字相加,代码如下:

利用位操作同样也可以做到:

2. 条件运算符的用法

通常我们都这样使用它:
x = (y < 0) ? 10 : 20;
但是同样也可以这样用:
(y < 0 ? x : y) = 20;

3. 在一个返回值为void 的函数中写一个return 语句

4. 逗号表达式的使用

通常逗号表达式会这样使用:

但是你可以在其他任何地方使用逗号表达式:

每条语句都进行了求值,但是表达式的值是最后一个语句的值。

5. 将结构体初始化为0

struct mystruct a = {0};

这将把结构体中全部元素初始化为0

6. 多字符常量

int x = 'ABCD';

这会把x的值设置为0x41424344(或者0x44434241,取决于架构)

7. printf 允许你使用变量来格式化格式说明符本身

* 符号可以达到这一目的

希望这些可以帮助到大家
此致敬礼

Vivek Nagarajan

你可以在奇怪的地方使用#include

如果你写:

fragment.c 包含:

这完全没有问题。只要#include 包含完整可解析的C表达式,预处理器并不在意它放在什么位置。

Vipul Mehta

1. printf 格式限定符中指定(POSIX扩展语法)

printf("%4$d %3$d %2$d %1$d", 1, 2, 3, 9); //将会打印9 3 2 1

2. 在scanf 中忽略输入输入

scanf("%*d%d", &a);// 如果输入1 2,则只会得到2

3. 在switch 中使用范围(gcc扩展语法)

4. 使用前缀ob 来限定常数,使其被当做二进制数(gcc扩展语法)

5.完全正确的最短的C语言程序

译注:虽然编译没有error但是却不能执行

Karan Bansal

scanf()的力量

假定我们有一个数组char a[100]
读取一个字符串:
scanf("%[^\n]\n", a);//表示一直读取直到遇到'\n',并且忽略掉'\n'

读取字符串直到遇到逗号:
scanf("%[^,]", a);//但是这次不会忽略逗号

如果你想忽略掉某个输入,使用在% 后使用*,如果你想要得到John Smith 的姓:

顺便提一句,你应该非常小心的使用scanf 因为它可能会是你的输入缓冲溢出!通常你应该使用fgetssscanf 而不是仅仅使用scanf,使用fgets 来读取一行,然后用sscanf 来解析这一行,就像上面演示的一样。

Afif Ahmed

~-n 等于n-1
-~n 等于n+1

原因:
当我们写-n时,实际上是以补码形式储存,所以-n 可以写成~n + 1,吧整个式子放在上面表达式的前面你就能明白原因了。

打赏支持我翻译更多好文章,谢谢!

打赏译者

打赏支持我翻译更多好文章,谢谢!

任选一种支付方式

3 7 收藏 18 评论

关于作者:艾凌风

初入职场小码农;翻译组的勤务员;C/Python/在线教育/英文翻译 个人主页 · 我的文章 · 76 ·    

相关文章

可能感兴趣的话题



直接登录
最新评论
  • jobbole很不错的翻译文章有很多,不像其它网站一个劲地复制别的Blog。

  • 薛非   2014/09/28

    pow(2,747) 太主观了
    依据不足

    • Cloud   2014/09/28

      这个依据的是浮动数在计算机中的表示方法

      • Dapper   2014/12/29

        但是即使是long double也只有12字节,一个12字节的整型都只能最大表示2^96即96位二进制有效位,对于一个浮点类型,还需要一部分位来储存指数和符号,绝对无法存在700+个二进制有效位的吧

        • JackPy   2015/06/18

          我去!忍不住要喷,你这计算机基础知识真挫,浮点数在计算机内部是怎么存的,何为阶码,何为尾数,回去多翻翻书,连定点和浮点都没分清,当然混淆了所能表示数的范围,更别说什么IEEE 754。 如果之前的基础教材都被拿去做废品卖的话,还可以去这篇blog里面去看看:http://www.ruanyifeng.com/blog/2010/06/ieee_floating-point_representation.html

          • dam githuber,自由职业人 2015/06/20

            我对浮点数的存储也不是很了解,但是我记得C99也并没有规定说float和double必须多少位吧,这都是所在的平台决定的,所以是不是像C最后能精确计算到的2的平方数也是用所在平台决定的呢?求教,说的话有错请指出

            • Dapper   2015/06/25

              C标准规定float至少要能表示6位有效数字,整数部分的表示范围至少要达到10^±37;规定double类型至少要能表示10位有效数字,整数部分的表示范围不小于float;
              通常float是32位而double是64位;然而Intel IA32处理器支持80位扩展精度,即float和double在IA32处理器上,可能会均被编译为80位扩展精度浮点数;
              而long double规定为精度和范围不小于double,常见的位数有80/96/128
              所以最终能够表示的范围在一定程度上取决于编译环境(编译器和CPU)

              • dam githuber,自由职业人 2015/07/07

                话说是哪个标准的哪一章节?如果方便的话告诉我页数,我看的是c99的type那一章并没提到,所以我想请教下,麻烦了,谢谢

          • Dapper   2015/06/24

            如果我有什么地方说错的,烦请请明确指出。IEEE规定的浮点数在内存中的储存方式我当然知道,既然说是有效位数了,那么就指的就是尾数在十进制下所能达到的最大位数,譬如32位浮点数的尾数占用23位,因而只有6位有效位数;64位浮点数尾数占用51位,则有15位有效位数;除去手动实现的大数操作,即使是long double也只能在某些环境下实现128位,纵然给你128位,让你全都来储存尾数,也不够表示700+位的十进制数,更何况还有阶码和符号位需要储存(不至于我不说阶码说指数就来吐槽我不懂浮点数吧),传统意义上的64位double阶码占用了10位,的确如文中所述能够表示到2^1023数量级,但是受限于尾数位数,所能够表示的有效数字(精度)是有限的,绝不可能达到700位精度(在多个平台上测试,均无法如文中一般输出准确结果,运算结果为74029831519160697000000000...)话说层主你有在自己电脑上运行过么?

            • 在这个语境下,你非要按十进制讨论“有效位数”,就有点呆萌了吧。

              IEEE 754 双精度浮点数是可以精确表示 2 ^ 1023 的,精确。
              0x7fe0000000000000

              如果偏要按你说的取“15位有效位数”,我想应该写成这样才好
              1.00000000000000 * 2 ^ 1023

              你说精确不精确?

              当然转换到十进制进行输出,就总有点误差了。
              8.9884656743115795e+307

              不太严格的类比,这就好像你十进制下精确的 0.3,存到浮点数里,也会有点误差。

              问题就在于这个精确的 2^1023 能不能精确地转换成十进制输出。不排除有 printf() 或者 dtoa() 的某种实现可以。

  • 不错,学习了

  • 薛非   2014/12/29

    但是同样也可以这样用:
    (y < 0 ? x : y) = 20;
    ===============
    无疑是错的。
    C标准说得清清楚楚,
    条件表达式得到的不是左值。

    • Dapper   2014/12/29

      应该是譬如(a+=2)%=p相当于a=(a+2)%p,这里的(a+=2)相当于返回a的引用,同理(...?x:y)返回的是x或y的引用(事实上不能算是引用,但是这么说比较直观吧)

      • JackPy   2015/06/18

        C里面有右值引用吗? 只有C++11才有,C里面表达式的值均不是左值,(y < 0 ? x : y) = 20;在C里面是编译通不过的,而C++可以

        • dam githuber,自由职业人 2015/06/20

          赞同,建议写这个博文的博主看看C语言的spec,别把C++和C混淆了

          • esxgx 学生党 2016/01/07

            其实这是gcc的一个特性:http://gcc.gnu.org/onlinedocs/gcc-3.4.6/gcc/Lvalues.html

  • sword C语言讲师 2015/06/20

    我有一个疑问,这些不常见的用法基本上都有容易理解的形式,却摆出了这么多的其他艰涩的形式,有何价值?

跳到底部
返回顶部