译注:本文摘编自 Quora 的一个热门问答贴。 请在linux系统下测试本文中出现的代码 Andrew Weimholt 的回复:
Brian Bi 的回复:1. 声明紧随用途之后理解声明有一条很简单的法则,不过不是什么“从左向右”这种没道理却到处宣传的法则。这一法则的观点是,一个声明是要告诉你,你所声明的对象要如何使用。例如:
更多详情请看这里: Brian Bi’s answer to C (programming language): Why doesn’t C use better notation for pointers? 2. 指定初始化:在C99之前,你只能按顺序初始化一个结构体。在C99中你可以这样做:
这段代码首先初始化了
3. 受限指针(C99):
编译器可能会假设,
如果你违反协议向 我猜想,引入这一特性最初的动机之一是想让C语言在数值计算时可以Fortran一样快。在Fortran 中,默认假定数组不会重叠,因此只有你通过 4. 静态数组索引(C99)在
中,你向编译器保证,你传递给 在
你不能修改指针 5. 泛型表达式(C11)这个表达式会在编译期间根据控制表达式的类型,在一个含有一个或多个备选方案的集合中做出选择。下面这个例子可以很好的说明这一切:
因此,如果 6. |
1 2 3 4 5 6 | #include <stdio.h> #include <math.h> int main() { printf ( "%.0f\n" , pow (2,747)); return 0; } |
输出结果:
1 | 740298315191606967520227188330889966610377319868419938630605715764070011466206019559325413145373572325939050053182159998975553533608824916574615132828322000124194610605645134711392062011527273571616649243219599128195212771328 |
答案:
这个问题包含两个部分。
其一,2的次方可以在double
中被准确的保存而不产生任何精度上的损失(这一结论直到2^1023都是对的,再往后就会产生上溢,得到一个正无穷的值)
另外一部分,很多人猜测是语言实现中的某些特殊情况导致的,但是实际上并非如此。的确,当输入的数据可以被2的某高次方整除时,有一部分代码被执行了,但是本质上这只是通常实现工作时的一个副作用。基本上,printf
在打印数字(任何类型)的时候只是做了从二进制到十进制的转换。并且由于结果对于浮点数可能会过大,printf
的内部实现包含和使用一个大整型实现,尽管在C中并没有大整型这种变量(在gcc源代码中,vfprintf.c
和dtoa.c
中包含了很多转换,如果你想要了解可以一看。)
如果你尝试打印3^474,
程序:
1 2 3 4 5 6 | #include <stdio.h> #include <math.h> int main() { printf ( "%.0f\n" , pow (3,474)); return 0; } |
输出结果:
14304567688284661153278974752312031583901259203711201647725006924333106634519194823303091330277684776547167093155518867557708479462413116497799842448027156309852771422896137582164841870381535840058702788340257784498862132559872 |
结果仍然是一个很大的数且位数也正确,但是这一次却不够精确。这里会产生一个相对误差,因为3^474不能以双精度浮点数准确的表示。准确的数应该是这样的143045676882846603471…
译注:在linux系统上是可以的,在windows 64位上后面会有很多0
我发现一些C语言特性或者是小技巧,我觉得只有很少的人知道。
因为printf()
函数返回它所打印的字符的个数,我们可以利用这一点来使数字相加,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include<stdio.h>; int add( int a, int b){ if ( if (a!=0&&b!=0)) return printf ( "%*c%*c" ,a, '\r' ,b, '\r' ); else return a!=0?a:b; } int main(){ int A = 0, B = 0; printf ( "Enter the two numbers to add\n" ); scanf ( "%d %d" ,&A,&B); printf ( "Required sum is %d" ,add(A,B)); return 0; } |
利用位操作同样也可以做到:
1 2 3 4 5 6 7 | int Add( int x, int y) { if (y == 0) return x; else return Add( x ^ y, (x & y) << 1); } |
通常我们都这样使用它:x = (y < 0) ? 10 : 20;
但是同样也可以这样用:(y < 0 ? x : y) = 20;
void
的函数中写一个return
语句1 2 3 4 5 6 7 8 9 | static void foo ( void ) { } static void bar ( void ) { return foo(); // 注意这里的返回语句. } int main ( void ) { bar(); return 0; } |
通常逗号表达式会这样使用:
1 2 3 4 | for ( int i=0; i<10; i++, doSomethingElse()) { /* whatever */ } |
但是你可以在其他任何地方使用逗号表达式:
1 | int j = ( printf ( "Assigning variable j\n" ), getValueFromSomewhere()); |
每条语句都进行了求值,但是表达式的值是最后一个语句的值。
struct mystruct a = {0};
这将把结构体中全部元素初始化为0
int x = 'ABCD';
这会把x的值设置为0×41424344(或者0×44434241,取决于架构)
printf
允许你使用变量来格式化格式说明符本身1 2 3 4 5 6 7 8 | #include <stdio.h> int main() { int a = 3; float b = 6.412355; printf ( "%.*f\n" ,a,b); return 0; } |
*
符号可以达到这一目的
希望这些可以帮助到大家
此致敬礼
你可以在奇怪的地方使用#include
如果你写:
1 2 3 4 5 6 7 | #include <stdio.h> void main() { printf #include "fragment.c" } |
且fragment.c
包含:
1 | ( "dayum!\n" ); |
这完全没有问题。只要#include
包含完整可解析的C表达式,预处理器并不在意它放在什么位置。
printf
格式限定符中指定(POSIX扩展语法)printf("%4$d %3$d %2$d %1$d", 1, 2, 3, 9); //将会打印9 3 2 1
scanf
中忽略输入输入scanf("%*d%d", &a);// 如果输入1 2,则只会得到2
switch
中使用范围(gcc扩展语法)1 2 3 4 5 | switch (c) { case 'A' ... 'Z' : //do something break ; case 1 ... 5 : //do something } |
ob
来限定常数,使其被当做二进制数(gcc扩展语法)1 | printf ( "%d" ,0b1101); // prints 13 |
1 | main; |
译注:虽然编译没有error但是却不能执行
scanf()
的力量假定我们有一个数组char a[100]
读取一个字符串:scanf("%[^\n]\n", a);//表示一直读取直到遇到'\n',并且忽略掉'\n'
读取字符串直到遇到逗号:scanf("%[^,]", a);//但是这次不会忽略逗号
如果你想忽略掉某个输入,使用在%
后使用*
,如果你想要得到John Smith
的姓:
1 | scanf ( "%s %s" , temp, last_name); //典型答案,使用一个临时变量 |
1 2 3 | scanf ( "%s" , last_name); scanf ( "%s" , last_name); // 另一种答案,使用一个变量但是调用两次 `scanf()` |
1 2 | scanf ( "%*s %s" , last); //最佳答案,因为你不需要额外的变量或是调用两次`scanf()` |
顺便提一句,你应该非常小心的使用scanf
因为它可能会是你的输入缓冲溢出!通常你应该使用fgets
和sscanf
而不是仅仅使用scanf
,使用fgets
来读取一行,然后用sscanf
来解析这一行,就像上面演示的一样。
~-n
等于n-1
-~n
等于n+1
原因:
当我们写-n
时,实际上是以补码形式储存,所以-n
可以写成~n + 1
,吧整个式子放在上面表达式的前面你就能明白原因了。