(转)C 语言中你想不到的一些问题

前言

自己虽然一直交叉的敲着 C 和 c plus plus 两种语言,但是其实自己就是使用一下常用的语法。
工作后又没有那么时间来看书,于是研究了一些C语言的细节来学习学习。

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

遍历数组

问题

有时候我们要遍历一个不知道大小的数组,但是我们有数组的名字,于是我们可以通过 sizeof 获得数组的大小了。
有了大小我们就可以遍历这个数组了。
一般情况下大家都是从下标 0 开始计数,于是从来不会遇到下面的问题。
如果你遇到下面的问题你能想出是什么原因吗?

代码

#include<stdio.h>

#define TOTAL_ELEMENTS (sizeof(array) / sizeof(array[0]))
int array[] = {23,34,12,17,204,99,16};

int main() {
    for(int d=-1; d <= (TOTAL_ELEMENTS-2); d++) {
        printf("%d\n",array[d+1]);
    }


    return 0;
}

输出

Process returned 0 (0x0)   execution time : 0.050 s
Press any key to continue.


end

分析

这个原因我一提,大家也都可以想到。
sizeof 返回的类型是 unsigned int .
unsigned int 与 int 进行运算还是 unsigned int。
然后 -1 和 unsigned int 比较,会先把 -1 转化为 unsigned int。
这样 -1 的 unsigned int 就很大了,所以没有输出了。

do while

问题

大家在 do while 中使用过 continue 吗?
没有的话来看看这个问题吧。

代码

#include<stdio.h>

int main() {
    int i=1;
    do {
        printf("%d\n",i);
        i++;
        if(i < 15) {
            continue;
        }
    } while(false);
    return 0;
}

输出

1

Process returned 0 (0x0)   execution time : 0.041 s
Press any key to continue.


end

分析

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

For the for loop, continue causes the conditional test and increment portions of the loop to execute.
For the while and do...while loops, program control passes to the conditional tests.

什么意思呢?
for 循环遇到 continue 会执行for 小括号内的第三个语句。
while 和 do...while 则会跳到循环判断的地方。

宏的展开

问题

大多数情况下,我们的宏定义常常是嵌套的。
这就涉及到展开宏定义的顺序了。
下面来看看其中一个问题。

代码

#include <stdio.h>

#define f(a,b) a##b
#define g(a)   #a
#define h(a) g(a)

int main() {
    printf("%s\n",h(f(1,2)));
    printf("%s\n",g(f(1,2)));
    return 0;
}

输出

12
f(1,2)

Process returned 0 (0x0)   execution time : 0.041 s
Press any key to continue.

end

分析

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

Macro arguments are completely macro-expanded before they are substituted into a macro body, unless they are stringified or pasted with other tokens.
After substitution, the entire macro body, including the substituted arguments, is scanned again for macros to be expanded.
The result is that the arguments are scanned twice to expand macro calls in them.

简单的说就是 宏会扫描一遍,把可以展开的展开,展开一次后会再扫描一次看又没有可以展开的宏。
下面我们模拟一下这个过程就可以明白了。

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

  ↓ 
> h(f(1,2))
    ↓ 
> g(f(1,2))
    ↓
> g(12)
  ↓
> g(12)
  ↓
> "12"

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

  ↓
> g(f(1,2))
  ↓
> "f(1,2)"

print 返回值

问题

你知道 printf 的返回值是什么吗?  
猜猜下面的代码输出是什么吧。

代码

#include <stdio.h>
int main() {
    int i=43;
    printf("%d\n",printf("%d",printf("%d",i)));
    return 0;
}

输出

4321

Process returned 0 (0x0)   execution time : 0.035 s
Press any key to continue.

end

分析

printf 的返回值是输出的字符的长度。
所以第一个输出 43 返回2.
第二个输出 2 返回 1. 第三个输出1. 于是输出的就是 4321 了。

数组参数

问题

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

代码

#include<stdio.h>
#define SIZE 10
void size(int arr[SIZE][SIZE]) {
    printf("%d %d\n",sizeof(arr),sizeof(arr[0]));
}

int main() {
    int arr[SIZE][SIZE];
    size(arr);
    return 0;
}

输出

4 40

Process returned 0 (0x0)   execution time : 0.039 s
Press any key to continue.


end

分析

对于第二个输出,应该是 40 这个大家都没有什么疑问的。
但是第一个是几呢?
你是不是想着是 400 呢?
答案是 4.
这是因为对于数组参数。第一级永远是指针形式。
也就是说数组参数永远是指针数组。
所以第一级永远是指针,而剩下的级数由于需要使用 [] 运算符, 所以不能是指针。

sizeof 的参数

问题

当我们有时候想让代码简洁点的时候,会把运算压缩到一起。
但是在 sizeof 中就要小心了。

代码

#include <stdio.h>
int main() {
    int i;
    i = 10;
    printf("i : %d\n",i);
    printf("sizeof(i++) is: %d\n",sizeof(i++));
    printf("i : %d\n",i);
    return 0;
}

输出

i : 10
sizeof(i++) is: 4
i : 10

Process returned 0 (0x0)   execution time : 0.039 s
Press any key to continue.


end

分析

你猜第二个i的输出时什么呢?
11 吗?
恭喜你,猜错了。

这个还需要查看文档了。
首先我的印象中 sizeof 是个宏,在编译器运算的。

The sizeof is a keyword, but it is a compile-time operator that determines the size, in bytes, of a variable or data type.

文档上说 sizeof 是一个关键字,但是在编译器运算。
所以编译器是不会进行我们的那些算术等运算的。
而是直接根据返回值推导类型,然后根据类型推导出大小的。

位运算左移

问题

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

代码

#include <stdio.h>
#define PrintInt(expr) printf("%s : %d\n",#expr,(expr))
int FiveTimes(int a) {
    return a<<2 + a;
}

int main() {
    PrintInt(FiveTimes(1));
    return 0;
}

输出

FiveTimes(1) : 8

Process returned 0 (0x0)   execution time : 0.624 s
Press any key to continue.



end

分析

需要我提示吗?
三个字:优先级

浮点数

问题

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

代码

#include <stdio.h>
int main() {
    float a = 12.5;
    printf("%d\n", a);
    printf("%d\n", *(int *)&a);
    return 0;
}

输出

12.500000
1095237632

Process returned 0 (0x0)   execution time : 0.651 s
Press any key to continue.


end

分析

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

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

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

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

00000000 00000000 00000000 00001010
0X0000000A

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

0X80000000 -2^31 
0XFFFFFFFF -1 负整数最小值
0X00000000 0
0X00000001 1 正整数最小值
0X7FFFFFFF 2^31-1 正整数最大值

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

对于一个浮点数,可以表示为 (-1)^S * X * 2^Y .
其中 S 是符号,使用一位表示。
X 是一个 二进制在 [1, 2) 内的小数,一般称为尾数,用23位表示。
Y 是一个整数,代表幂,一般称为阶码,用8位表示。

其中 Y 又涉及符号问题了。
8位的Y可以表示0到255,取指数偏移值 127 (2^(8-1) - 1)作为分界线,小于127的数是负数,大于的是正数。
这里我不明白为什么不使用以前数字的表示方法。

比如 12.5 的二进制是 1100.1 。
转化为上面的公式就是 (-1)^0 * 1.1001 * 2^3

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

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

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

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

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

0 10000010 10010000000000000000000
01000001 01001000 00000000 00000000

然后这个二进制对应着整数 1095237632 。
这样一切都解释清楚了。

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

你可能感兴趣的:((转)C 语言中你想不到的一些问题)