C语言有哪些鲜为人知的特性?
译注:本文摘编自 Quora 的一个热门问答贴。 请在linux系统下测试本文中出现的代码
Andrew Weimholt 的回复:
switch
语句中的case
关键词可以放在if-else
或者是循环当中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
switch
{ case
// if
{ case
// } else
{ // for
{ case
// } } break ; case
|
Brian Bi 的回复:
1. 声明紧随用途之后
理解声明有一条很简单的法则,不过不是什么“从左向右”这种没道理却到处宣传的法则。这一法则的观点是,一个声明是要告诉你,你所声明的对象要如何使用。例如:
1
2
3
4
|
int
/* int
/* int
/* int
/* |
更多详情请看这里: Brian Bi’s answer to C (programming
language): Why doesn’t C use better notation for pointers?
2. 指定初始化:
在C99之前,你只能按顺序初始化一个结构体。在C99中你可以这样做:
1
2
3
4
5
6
|
struct
int
int
int
}; Foo |
这段代码首先初始化了foo.z
,然后初始化了foo.x
. foo.y
没有被初始化,所以被置为0。
这一语法同样可以被用在数组中。以下三行代码是等价的:
1
2
3
|
int
int
int
|
3. 受限指针(C99):
restrict
关键词是一个限定词,可以被用在指针上。它向编译器保证,在这个指针的生命周期内,任何通过该指针访问的内存,都只能被这个指针改变。比如,在
1
2
3
4
5
6
|
int
const
* int * (*y)++; int
(*y)--; return
} |
编译器可能会假设,x
和y
所指的并不是同一个int
对象,因为如果它们指向了同一个对象,则x
的值将可以通过y
修改,这正是你保证不会发生的。因此,将允许编译器来优化f
,就好像函数原本被写做如下这样:
1
2
3
|
int
const
* int * return
} |
如果你违反协议向f
传递两个指向同一int对象的指针时,将产生未定义行为。
我猜想,引入这一特性最初的动机之一是想让C语言在数值计算时可以Fortran一样快。在Fortran 中,默认假定数组不会重叠,因此只有你通过restrict
限定词来显式的告诉编译器数组不能重叠,编译器才能在C语言中进行这样的优化。
4. 静态数组索引(C99)
在
1
2
3
|
void
int
static
/* } |
中,你向编译器保证,你传递给f
的指针指向一个具有至少10个int
类型元素的数组的首个元素。我猜这也是为了优化;例如,编译器将会假定a
非空。编译器还会在你尝试要将一个可以被静态确定为null的指针传入或是一个数组太小的时候发出警告。
在
1
2
3
|
void
int
const ]) /* } |
你不能修改指针a.
,这和说明符int
作用是一样的。然而,当你结合上一段中提到的
* const a.static
使用,比如在int
中,你可以获得一些使用指针风格无法得到的东西。
a[static const 10]
5. 泛型表达式(C11)
这个表达式会在编译期间根据控制表达式的类型,在一个含有一个或多个备选方案的集合中做出选择。下面这个例子可以很好的说明这一切:
1
2
3
4
5
|
#define long
: default : float : )(X) |
因此,如果expr
是long
类型的,
doublecbrt(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次方而不产生误差。这是为什么呢?
程序:
1
2
3
4
5
6
|
#include #include int
printf ( "%.0f\n" , pow (2,747)); return
} |
输出结果:
1
|
740298315191606967520227188330889966610377319868419938630605715764070011466206019559325413145373572325939050053182159998975553533608824916574615132828322000124194610605645134711392062011527273571616649243219599128195212771328 |
答案:
这个问题包含两个部分。
其一,2的次方可以在double
中被准确的保存而不产生任何精度上的损失(这一结论直到2^1023都是对的,再往后就会产生上溢,得到一个正无穷的值)
另外一部分,很多人猜测是语言实现中的某些特殊情况导致的,但是实际上并非如此。的确,当输入的数据可以被2的某高次方整除时,有一部分代码被执行了,但是本质上这只是通常实现工作时的一个副作用。基本上,printf
在打印数字(任何类型)的时候只是做了从二进制到十进制的转换。并且由于结果对于浮点数可能会过大,printf
的内部实现包含和使用一个大整型实现,尽管在C中并没有大整型这种变量(在gcc源代码中,vfprintf.c
和dtoa.c
中包含了很多转换,如果你想要了解可以一看。)
如果你尝试打印3^474,
程序:
1
2
3
4
5
6
|
#include #include int
printf ( "%.0f\n" , pow (3,474)); return
} |
输出结果:
14304567688284661153278974752312031583901259203711201647725006924333106634519194823303091330277684776547167093155518867557708479462413116497799842448027156309852771422896137582164841870381535840058702788340257784498862132559872 |
结果仍然是一个很大的数且位数也正确,但是这一次却不够精确。这里会产生一个相对误差,因为3^474不能以双精度浮点数准确的表示。准确的数应该是这样的143045676882846603471…
译注:在linux系统上是可以的,在windows 64位上后面会有很多0
Utkal Sinha
我发现一些C语言特性或者是小技巧,我觉得只有很少的人知道。
1. 不使用加号来使数字相加
因为printf()
函数返回它所打印的字符的个数,我们可以利用这一点来使数字相加,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#include<stdio.h>; int
int
int
if ( if (a!=0&&b!=0)) return
( "%*c%*c" ,a, '\r' ,b, '\r' ); else
} int
int
printf ( "Enter ); scanf ( "%d ,&A,&B); printf ( "Required ,add(A,B)); return
} |
利用位操作同样也可以做到:
1
2
3
4
5
6
7
|
int
int
int
{ if
return
else return
} |
2. 条件运算符的用法
通常我们都这样使用它:
x = (y < 0) ? 10 : 20;
但是同样也可以这样用:
(y < 0 ? x : y) = 20;
3. 在一个返回值为void
的函数中写一个return
语句
1
2
3
4
5
6
7
8
9
|
static
void ) static
void ) return
// } int
void ) bar(); return
} |
4. 逗号表达式的使用
通常逗号表达式会这样使用:
1
2
3
4
|
for
int
{ /* } |
但是你可以在其他任何地方使用逗号表达式:
1
|
int
printf ( "Assigning ), |
每条语句都进行了求值,但是表达式的值是最后一个语句的值。
5. 将结构体初始化为0
struct mystruct a = {0};
这将把结构体中全部元素初始化为0
6. 多字符常量
int x = 'ABCD';
这会把x的值设置为0×41424344(或者0×44434241,取决于架构)
7. printf
允许你使用变量来格式化格式说明符本身
1
2
3
4
5
6
7
8
|
#include int
int
float
printf ( "%.*f\n" ,a,b); return
} |
*
符号可以达到这一目的
希望这些可以帮助到大家
此致敬礼
Vivek Nagarajan
你可以在奇怪的地方使用#include
如果你写:
1
2
3
4
5
6
7
|
#include void
{ printf #include } |
且fragment.c
包含:
1
|
( "dayum!\n" ); |
这完全没有问题。只要#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扩展语法)
1
2
3
4
5
|
switch (c) case
'Z' : //do break ; case
//do } |
4. 使用前缀ob
来限定常数,使其被当做二进制数(gcc扩展语法)
1
|
printf ( "%d" ,0b1101); // |
5.完全正确的最短的C语言程序
1
|
main; |
译注:虽然编译没有error但是却不能执行
Karan Bansal
scanf()
的力量
假定我们有一个数组char a[100]
读取一个字符串:
scanf("%[^\n]\n", a);//表示一直读取直到遇到'\n',并且忽略掉'\n'
读取字符串直到遇到逗号:
scanf("%[^,]", a);//但是这次不会忽略逗号
如果你想忽略掉某个输入,使用在%
后使用*
,如果你想要得到John
的姓:
Smith
1
|
scanf ( "%s , //典型答案,使用一个临时变量 |
1
2
3
|
scanf ( "%s" , scanf ( "%s" , // |
1
2
|
scanf ( "%*s , //最佳答案,因为你不需要额外的变量或是调用两次`scanf()` |
顺便提一句,你应该非常小心的使用scanf
因为它可能会是你的输入缓冲溢出!通常你应该使用fgets
和sscanf
而不是仅仅使用scanf
,使用fgets
来读取一行,然后用sscanf
来解析这一行,就像上面演示的一样。
Afif Ahmed
~-n
等于n-1
-~n
等于n+1
原因:
当我们写-n
时,实际上是以补码形式储存,所以-n
可以写成~n
,吧整个式子放在上面表达式的前面你就能明白原因了。
+ 1
C语言有哪些鲜为人知的特性?的更多相关文章
- 文本编辑利器Notepad++ 10个强大而又鲜为人知的特性【转】
文本编辑利器Notepad++ 10个强大而又鲜为人知的特性 - 为程序员服务
- Google Guava的5个鲜为人知的特性
译文出处: 花名有孚 原文出处:takipi.com Google Guava有哪些比较冷门但却又实用的特性呢? 它是最流行的开源库之一,你应该听过它的大名,它诞生的地方正是人们举办真正的魁地奇比 ...
- (转)C++语言的15个晦涩特性
原文链接: Evan Wallace 翻译: 伯乐在线- 敏敏 译文链接: http://blog.jobbole.com/54140/ 这个列表收集了 C++ 语言的一些晦涩(Obscure)特 ...
- C++你不知道的那些事儿—C++语言的15个晦涩特性
这个列表收集了 C++ 语言的一些晦涩(Obscure)特性,是我经年累月研究这门语言的各个方面收集起来的.C++非常庞大,我总是能学到一些新知识.即使你对C++已了如指掌,也希望你能从列表中学到一些 ...
- Go 语言简介(下)— 特性
希望你看到这篇文章的时候还是在公交车和地铁上正在上下班的时间,我希望我的这篇文章可以让你利用这段时间了解一门语言.当然,希望你不会因为看我的文章而错过站.呵呵. 如果你还不了解Go语言的语法,还请你移 ...
- Go语言入门(一)特性、安装、环境搭建、第一个程序等
Go语言是谷歌推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性.谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发Go,是因为过去10多年间软件开发的难度令人沮 ...
- Python语言的2个典型特性
[动态编程语言] 学习过C.C++的同学都知道,使用这二者编写代码时,变量在使用前必须通过类似"int a=123"的语句提取申明,然后在真正赋值的时候校验取值跟类型是否相符.也就 ...
- 面试问题之C++语言:面向对象的三大特性
转载于:https://www.cnblogs.com/BEN-LK/p/10720249.html 面向对象的三大特性:封装.继承.多态 封装:就是把客观事物封装成抽象的类,并且类可以把自己的数据和 ...
- 转:C++语言的15个晦涩特性
转自 http://blog.jobbole.com/54140/ 操作符重载和检查顺序 重载,(逗号),||或者&&操作符会引起混乱,因为它打破了正常的检查规则.通常情况下,逗号操作 ...
随机推荐
- 插件开发之360 DroidPlugin源码分析(五)Service预注册占坑
请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52264977 在了解系统的activity,service,broa ...
- 程序员必须搞清的概念-equals和=和hashcode的区别
1. 首先equals()和hashcode的介绍 equals 方法在非空对象引用上实现相等关系: * 自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true. * 对称性:对于 ...
- HTML5中 HTML表单和PHP环境搭建及与PHP交互 韩俊强的博客
每日更新关注:http://weibo.com/hanjunqiang 新浪微博! 知识点概括:HTML表单/PHP环境搭建/表单提交数据与PHP交互 第一部分:HTML表单 <!DOCTYP ...
- Android异常:android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original
Android异常:android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that cr ...
- Android 震动马达系统
Android之 看"马达"如何贯通Android系统 (从硬件设计 --> 驱动 --> HAL --> JNI --> Framework --> ...
- Sprite添加阴影摇摆动画(Unity3D开发之九)
猴子原创,欢迎转载.转载请注明: 转载自Cocos2D开发网–Cocos2Dev.com,谢谢! 原文地址: http://www.cocos2dev.com/?p=575 今天看到一个很简单的摇摆动 ...
- Java-IO之对象输入流输出流(ObjectInputStream和ObjectOutputStream)
ObjectInputStream和ObjectOutputStream的作用是对基本数据和对象进行序列化操作支持.创建文件输出流对应的ObjectOutputStream对象,该ObjectOutp ...
- 并发编程(三): 使用C++11实现无锁stack(lock-free stack)
前几篇文章,我们讨论了如何使用mutex保护数据及使用使用condition variable在多线程中进行同步.然而,使用mutex将会导致一下问题: 等待互斥锁会消耗宝贵的时间 - 有时候是很多时 ...
- Java进阶(二十九)Could not create the view: An unexpected exception was thrown
Could not create the view: An unexpected exception was thrown 在将web项目部署到tomcat时,控制台输出以下内容: 这个问题的出现是在 ...
- C++ Primer 有感(标准库set类型)
set容器只是单纯的键的集合,键必须为一.set容器不支持下标操作,而且没有定义maped_type类型.在set容器中,value_type不是pair类型,而是与key_type类型相同的类型. ...