配色: 字号:
C 语言中你想不到的一些问题
2014-12-12 | 阅:  转:  |  分享 
  
2014/12/12C语言中你想不到的一些问题

http://github.tiankonguse.com/blog/2014/12/05/c-base/1/12

前言

自己虽然一直交叉的敲着C和cplusplus两种语言,但是其实自己就是使用一下常用的语法。

工作后又没有那么时间来看书,于是研究了一些C语言的细节来学习学习。

建议看的时候先不要看问题分析,这样才能考察自己到底会不会的。

遍历数组

问题

有时候我们要遍历一个不知道大小的数组,但是我们有数组的名字,于是我们可以通过sizeof获得数组的大小了。

有了大小我们就可以遍历这个数组了。

一般情况下大家都是从下标0开始计数,于是从来不会遇到下面的问题。

如果你遇到下面的问题你能想出是什么原因吗?

代码

1.#include2.

3.#defineTOTAL_ELEMENTS(sizeof(array)/sizeof(array[0]))4.

intarray[]={23,34,12,17,204,99,16};5.

6.intmain(){7.

for(intd=-1;d<=(TOTAL_ELEMENTS-2);d++){8.

printf("%d\n",array[d+1]);9.

}10.

11.12.

return0;13.

}

输出

1.2.

Processreturned0(0x0)executiontime:0.050s3.

Pressanykeytocontinue.4.

5.6.

7.8.

9.10.

2014/12/12C语言中你想不到的一些问题

http://github.tiankonguse.com/blog/2014/12/05/c-base/2/12

11.12.

13.14.

15.16.

17.18.

19.20.

end

分析

这个原因我一提,大家也都可以想到。

sizeof返回的类型是unsignedint.

unsignedint与int进行运算还是unsignedint。

然后-1和unsignedint比较,会先把-1转化为unsignedint。

这样-1的unsignedint就很大了,所以没有输出了。

dowhile

问题

大家在dowhile中使用过continue吗?

没有的话来看看这个问题吧。

代码

1.#include2.

3.intmain(){4.

inti=1;5.

do{6.

printf("%d\n",i);7.

i++;8.

if(i<15){9.

continue;10.

}11.

}while(false);12.

return0;13.

}

输出

2014/12/12C语言中你想不到的一些问题

http://github.tiankonguse.com/blog/2014/12/05/c-base/3/12

1.12.

3.Processreturned0(0x0)executiontime:0.041s4.

Pressanykeytocontinue.5.

6.7.

8.9.

10.11.

12.13.

14.15.

16.end

分析

这个需要查看文档了:continue会到循环的哪个地方继续运行。

于是我查看了官方文档

Fortheforloop,continuecausestheconditionaltestandincrement

portionsofthelooptoexecute.

Forthewhileanddo...whileloops,programcontrolpassestothe

conditionaltests.

什么意思呢?

for循环遇到continue会执行for小括号内的第三个语句。

while和do...while则会跳到循环判断的地方。

宏的展开

问题

大多数情况下,我们的宏定义常常是嵌套的。

这就涉及到展开宏定义的顺序了。

下面来看看其中一个问题。

代码

2014/12/12C语言中你想不到的一些问题

http://github.tiankonguse.com/blog/2014/12/05/c-base/4/12

1.#include2.

3.#definef(a,b)a##b4.

#defineg(a)#a5.

#defineh(a)g(a)6.

7.intmain(){8.

printf("%s\n",h(f(1,2)));9.

printf("%s\n",g(f(1,2)));10.

return0;11.

}

输出

1.122.

f(1,2)3.

4.Processreturned0(0x0)executiontime:0.041s5.

Pressanykeytocontinue.6.

7.8.

9.10.

11.12.

13.14.

15.16.

17.end

分析

这个问题又需要查看文档了:Macro是怎么展开的。

于是我查看了官方文档

Macroargumentsarecompletelymacro-expandedbeforetheyare

substitutedintoamacrobody,unlesstheyarestringifiedorpastedwith

othertokens.

Aftersubstitution,theentiremacrobody,includingthesubstituted

arguments,isscannedagainformacrostobeexpanded.

Theresultisthattheargumentsarescannedtwicetoexpandmacrocallsin

them.

简单的说就是宏会扫描一遍,把可以展开的展开,展开一次后会再扫描一次看又没有可以展开的宏。

2014/12/12C语言中你想不到的一些问题

http://github.tiankonguse.com/blog/2014/12/05/c-base/5/12

下面我们模拟一下这个过程就可以明白了。

对于第一个,是下面的过程。

1.↓2.

>h(f(1,2))3.

↓4.

>g(f(1,2))5.

↓6.

>g(12)7.

↓8.

>g(12)9.

↓10.

>"12"

对于第二个,是这个过程。

1.↓2.

>g(f(1,2))3.

↓4.

>"f(1,2)"

print返回值

问题

你知道printf的返回值是什么吗?

猜猜下面的代码输出是什么吧。

代码

1.#include2.

intmain(){3.

inti=43;4.

printf("%d\n",printf("%d",printf("%d",i)));5.

return0;6.

}

输出

1.4321

2014/12/12C语言中你想不到的一些问题

http://github.tiankonguse.com/blog/2014/12/05/c-base/6/12

2.3.

Processreturned0(0x0)executiontime:0.035s4.

Pressanykeytocontinue.5.

6.7.

8.9.

10.11.

12.13.

14.15.

16.17.

18.end

分析

printf的返回值是输出的字符的长度。

所以第一个输出43返回2.

第二个输出2返回1.第三个输出1.于是输出的就是4321了。

数组参数

问题

对于函数传参为数组时,你知道到底传的是什么吗?

代码

1.#include2.

#defineSIZE103.

voidsize(intarr[SIZE][SIZE]){4.

printf("%d%d\n",sizeof(arr),sizeof(arr[0]));5.

}6.

7.intmain(){8.

intarr[SIZE][SIZE];9.

size(arr);10.

return0;11.

}

输出

2014/12/12C语言中你想不到的一些问题

http://github.tiankonguse.com/blog/2014/12/05/c-base/7/12

1.4402.

3.Processreturned0(0x0)executiontime:0.039s4.

Pressanykeytocontinue.5.

6.7.

8.9.

10.11.

12.13.

14.15.

16.17.

18.end

分析

对于第二个输出,应该是40这个大家都没有什么疑问的。

但是第一个是几呢?

你是不是想着是400呢?

答案是4.

这是因为对于数组参数。第一级永远是指针形式。

也就是说数组参数永远是指针数组。

所以第一级永远是指针,而剩下的级数由于需要使用[]运算符,所以不能是指针。

sizeof的参数

问题

当我们有时候想让代码简洁点的时候,会把运算压缩到一起。

但是在sizeof中就要小心了。

代码

1.#include2.

intmain(){3.

inti;4.

i=10;5.

printf("i:%d\n",i);6.

printf("sizeof(i++)is:%d\n",sizeof(i++));7.

printf("i:%d\n",i);8.

return0;

2014/12/12C语言中你想不到的一些问题

http://github.tiankonguse.com/blog/2014/12/05/c-base/8/12

9.}

输出

1.i:102.

sizeof(i++)is:43.

i:104.

5.Processreturned0(0x0)executiontime:0.039s6.

Pressanykeytocontinue.7.

8.9.

10.11.

12.13.

14.15.

16.17.

end

分析

你猜第二个i的输出时什么呢?

11吗?

恭喜你,猜错了。

这个还需要查看文档了。

首先我的印象中sizeof是个宏,在编译器运算的。

Thesizeofisakeyword,butitisacompile-timeoperatorthatdetermines

thesize,inbytes,ofavariableordatatype.

文档上说sizeof是一个关键字,但是在编译器运算。

所以编译器是不会进行我们的那些算术等运算的。

而是直接根据返回值推导类型,然后根据类型推导出大小的。

位运算左移

问题

2014/12/12C语言中你想不到的一些问题

http://github.tiankonguse.com/blog/2014/12/05/c-base/9/12

这个问题没什么说的,你运行一下就会先感到诧异,然后会感觉确实应该是这个样字,甚至会骂这代码写的太不规范

了。

代码

1.#include2.

#definePrintInt(expr)printf("%s:%d\n",#expr,(expr))3.

intFiveTimes(inta){4.

returna<<2+a;5.

}6.

7.intmain(){8.

PrintInt(FiveTimes(1));9.

return0;10.

}

输出

1.FiveTimes(1):82.

3.Processreturned0(0x0)executiontime:0.624s4.

Pressanykeytocontinue.5.

6.7.

8.9.

10.11.

12.13.

14.15.

16.end

分析

需要我提示吗?

三个字:优先级

浮点数

问题

大家经常使用浮点数,知道背后的原理吗?

2014/12/12C语言中你想不到的一些问题

http://github.tiankonguse.com/blog/2014/12/05/c-base/10/12

代码

1.#include2.

intmain(){3.

floata=12.5;4.

printf("%d\n",a);5.

printf("%d\n",(int)&a);6.

return0;7.

}

输出

1.12.5000002.

10952376323.

4.Processreturned0(0x0)executiontime:0.651s5.

Pressanykeytocontinue.6.

7.8.

9.10.

11.12.

13.14.

15.16.

17.end

分析

为了方便大家测试,我提供了一个32位浮点型向二进制的转换器

首先int和float在32位机器上都是四字节的。

对于整数储存,大家都没什么疑问。

比如10的二进制,十六进制如下

1.000000000000000000000000000010102.

0X0000000A

2014/12/12C语言中你想不到的一些问题

http://github.tiankonguse.com/blog/2014/12/05/c-base/11/12

由于最高位代表符号,所以整数可以表示的范围就是

1.0X80000000-2^312.

0XFFFFFFFF-1负整数最小值3.

0X0000000004.

0X000000011正整数最小值5.

0X7FFFFFFF2^31-1正整数最大值

上面的二进制也就决定了4字节的整数范围是-2^31到2^31-1.

对于一个浮点数,可以表示为(-1)^SX2^Y.

其中S是符号,使用一位表示。

X是一个二进制在[1,2)内的小数,一般称为尾数,用23位表示。

Y是一个整数,代表幂,一般称为阶码,用8位表示。

其中Y又涉及符号问题了。

8位的Y可以表示0到255,取指数偏移值127(2^(8-1)-1)作为分界线,小于127的数是负数,大于的是正数。

这里我不明白为什么不使用以前数字的表示方法。

比如12.5的二进制是1100.1。

转化为上面的公式就是(-1)^01.10012^3

下面我们来推导一下这个数字的二进制是什么吧。

符号为正,所以第一位就是0了

3+127就是130了。于是使用10000010可以表示。

1.1001一般不表示小数前的1,于是只需要表示1001即可,于是使用10010000000000000000000就可以表示

了。

于是12.5的float的二进制表示就推算出来了

2014/12/12C语言中你想不到的一些问题

http://github.tiankonguse.com/blog/2014/12/05/c-base/12/12

1.010000010100100000000000000000002.

01000001010010000000000000000000

然后这个二进制对应着整数1095237632。

这样一切都解释清楚了。

当然还要注意一个问题,这里有这么一个特殊规定:阶码Y如果是0,尾数X就不再加1了。

作者「tiankonguse」于2014-12-0514:15:00更新本文」于2014-12-0500:00:00发布本文

文章声明:自由转载-非商用-非衍生-保持署名|BY-NC-SA

如果你觉得这篇文章对你有帮助,欢迎自由付费:

献花(0)
+1
(本文系icecity0079...首藏)