[C跟指针]第一部分

[C和指针]第一部分
声明:原创作品转载时请注明文章来自SAP师太博客并以超链接形式标明文章原始出处否则将追究法律责任!

第一章     快速上手... 1
第二章     数据... 2
第三章     操作符... 6

第一章     快速上手


要从逻辑上删除一段C代码,更好的办法是使用 #if 指令,这样即使这段代码之间原生存在注释(/**/)也没问题(/**/块注释是不能嵌套的),这是一种安全的方法:
#if 0
         statements
#endif
 
#include <stdio.h>:这是一种预处理指令,由预处理器解释。预处理器使用stdio.h的库函数头文件的内容替换预处理指令,其结果就像stdio.h的内容被逐字写到源文件的那个位置。另一种预处理指令 #define Java中定义的public final static int XX 定义的静态的final常量。
 
如果你有一些声明需要被几个不同的源文件使用,你可以把这些声明编写在一个头文件中,然后在需要使用的源文件使用 #include 预处理指令来包含这些声明,这样就只需要一份拷贝,便于后期的维护。
 
函数原型便于编译器在调用它们进行准确性检查。
 
假如某个程序的代码由几个源文件所组成,那么使用其他源文件的源文件需要写明被使用源文件中函数的原型。把原型放在头文件中并使用#include指令包含它们,可以避免多份拷贝。
 
数组名就是第一个元素的地址,一般不需要在数组名前加上 & ,但你也可以加上也是没有问题的。
 
scanf是以空白字符为间隔的(除了%c以外),即使字符串也是一样,所以字符串中不能包含空白字符
 
标准并未规定编译器对数组下标的有效性进检查,所以在使用前一般需要自已进行有效性检查,否则会破坏数组后面附近的内存数据。
 
scanf函数每次调用时都从标准输入读取相应的类型数据(主要是根据格式串来读取),如果转换失败,不管是因为文件已经结尾还是因为下一次输入的字符无法转换为整数,函数都会非1。如果正常读取,则返回个数1
 
gets函数从标准输入读取一行文本并把它存储在一个字符数组中,它会丢弃换行符,并在该行的结尾加上一个空字符 \0
 
puts函数是gets函数的输出版本,它把指定的字符串写到标准输出并在末尾添上一个换行符。
 
getchar函数从标准输入读取一个字符并返回它的值,如果输入中不再存在任何字符,函数就会返回常量EOF-1,在stdio.h中定义),用于提示文件的结尾。getchar函数返回的是字符的ASCII码整数。
 
putchar将整型变量的内容以字符的形式打印出来,通常显示在屏幕上
 

C运行过程


编辑 –> 预处理 –> 编译(编译成目标代码) –> 连接(连接目标代码与库,生成a.out可执行文件) –> 加载(将程序从磁盘加载到内存) –> 运行
 
连接程序把目标代码和库函数的代码连接起来产生可执行的映象。
 
在基于UNIXC系统中,编译和连接程序的命令是cc,如编译和连接welcome.c的程序,在UNIX提示符下键入:
cc welcome.c
就会产生一个名为a.out的文件,该文件是程序welcome.c的可执行映象,在命令提示符中输入a.out就可以执行程序了。
 
 

第二章     数据


长整型至少应该和整型一样长,而整型至少应该和短整型一样长。
 
ANSI标准则加入了一个规范,说明了各种整型值的最小允许范围:

类型

最小范围

char

0127

signed char

-127127

unsigned char

0255

short int

-3276732767

unsigned short int

065535

int

-3276732767

unsigned int

065535

long int

-21474836472147483647

unsigned long int

04294967295

short int至少16位,long int至少32位,至于缺省的int究竟是16位还是32位,或者是其他值,则由编译器设计都决定。通常这个选择缺省值是这种机器最为自然高效的位数。同时,ANSI并没有规定3个值必须不一样,如果某种机型环境的字长为32位,则完全可能把这3个整型值都设定为32位。
 
limits.h文件中说明了各种不同整数类型特点:
#define CHAR_BIT  8                 //字符的位数(至少8位)
#define SCHAR_MIN (-128)            //signed char最小值
#define SCHAR_MAX 127               //signed char最大值
#define UCHAR_MAX 255               //unsigned char最大值
#if ('\x80' < 0) //看一个字节能否能够存储 -128,如果能够,则缺省的字符就是有符号的,否则就是无符号的,一般缺省的char为有符号字符
#define CHAR_MIN   SCHAR_MIN
#define CHAR_MAX   SCHAR_MAX
#else
#define CHAR_MIN   0
#define CHAR_MAX   UCHAR_MAX
#endif
 
#define SHRT_MAX  32767             //short int最大值
#define SHRT_MIN  (-SHRT_MAX-1)     //short int最小值
#define USHRT_MAX 0xffff            //unsigned short int最大值
 
#define INT_MAX      2147483647    //int最大值
#define INT_MIN      (-INT_MAX-1) //int最大值
#define UINT_MAX  0xffffffff        //unsigned int最大值
 
#define LONG_MAX  2147483647L       //long int最大值
#define LONG_MIN  (-LONG_MAX-1)     //long int最大值
#define ULONG_MAX 0xffffffffUL      //unsignedlong int最大值
 
short intintlong int缺省就是有符号的,而char缺省是否有符号取决于编译器,需要根据limits.h头文件的条件预处理来决定,一般也是有符号的。所以signed关键字一般只用于char类型,因为其他整型类型在缺省情况下都是有符号的。
 
当可移植问题比较重要时,字符类型 char 是否为有符号就会带来问题,最好的方案就是把存储于char型变量的值限制在signed char unsigned char的交集内,只有位于这个区间的字符才可移值,ASCII字符集中的字符都是位于这个范围之内,所以是可移植的。
 
当一个程序内出现整型字面值时,它是属于整型家族9种不同类型中的哪一种呢?答案取决于字面值是如何书写的,但是你可以在有些字面值的后面添加一个后缀来改变缺省的规则。Llong整型值,U用于把数值指定为unsigned整型值。
 
十进制整型字面值可能是intlongunsigned long,在缺省情况下,它是最短类型但又能完整容纳这个值的类型
 
从技术上讲,-275并非字面值常量,而是常量表达式。负号被解释为单目操作符而不是数值的一部分。但在实践中,这个歧义性基本没有什么意义,这个表达式总是被编译器按照你所预想的方式计算。
 
八进制和十六进制字面值可能的类型是intunsigned intlongunsigned long,在缺省情况下,字面值的类型就是上述类型中最短但足以容纳整个值的类型
 
字符常量的类型总是int,你不能在它后面添加UL后缀。如果一个多字节字符常量的前面有一个L,那么它就是宽字符常量。如:L'x'
 
ANSI C标准仅仅规定long double 至少和double一样长,而double至少和float一样长。标准同时规定了一个最小范围:所有浮点类型至少能够容纳从10-371037之间的任何值。
 
浮点数字面值在缺省情况下都是double类型的,除非它的后面跟一个L表示是一个long double类型值,或跟一个F表示它是一个float类型的值
 
字符串通常存储在字符数组中,这也是C语言没有的字符串类型的原因。由于NUL字节是用于终结字符串的,所以在字符串内部不能有NUL字节,不过,在一般情况下,这个限制并不会造成问题,之所以选择NUL作为字符串的终止符,是因为它不是一个可打印的字符。
 
""表示一个空的字符串常量,即使是空字符串,依然存在作为终止符的NUL字节。
 
NULASCII字符集中 '\0' 字符的名字,它的字节模式为全0NULL指一个其值为0的指针。符号NULL在头文件stdio.h中定义,另一方面,并不存在预定义的符号NUL,所以如果你想使用它而不是字符常量'\0',你必须自行定义。
 
K&R C中,没有提及一个字符串常量中的字符是否可以被程序修改,但清楚地表明具有相同的值的不同字符串常量在内存中是分开存储的。因此,许多编译器都允许程序修改字符串常量;但ANSI C则声明如果对一个字符串常量进行修改,其结果是未定义的,它把一个字符串常量存储于一个地方,即使它在程序中多次出现,所以在程序中最好不要修改字符串常量。
 
int* a, b, c;
人们很自然地以为这条语句把所有三个变量声明为指向整型的指针,但事实上并非如此,星号实际上是表达 *a 的一部分,只对这个标识符有用,其他两个变量则只是普通的整型。
 
char *message = "Hello world!";
这条语句把message声明为一个指向字符的指针,并用字符串常量中第一个字符的地址对该指针进行初始化。这里看上去初始化似乎是赋给表达式 *message,事实上它是赋给message本身的。换句话说,前面一个声明相当于:
    char *message;
    message = "Hello world!";
 
隐式声明:C语言中有几种声明,它的类名可以省略。例如,函数如果不地声明返回类型,它就默认返回整型。使用旧网格声明函数的形式参数时,如果省略了参数的类型,编译器就默认它们为整型。最后,如果编译器可以推断出一条语句实际上是一个声明时,如果它缺省类型名,编译器会假定它为整型:
    int a[10];
    int c;
    b[10];
    d;
f( x) {
    return x + 1;
}
上面只能在 K&R C 标准通过编译,在ANSI C是非法的。
 
常量定义的两种方式:
#define MAX_LENGTH 10
intconst max_length = 10;
一旦定义并初始化后,都不能修改其值,但是第一种更好。
 
4种不同的作用域:文件作用域、函数作用域、代码块作用域、原型作用域。
 
位于一对花括号之间的所有语句称为一个代码块。任何在代码块的开始位置声明的标识符都具有代码块作用域。如果代码块处于嵌套状态,并且内层以代码块有一个标识符的名字与外层相同,则内层的那个标识符会隐藏外层的标识符。但是,如果在函数体内部声明了名字与形参相同的局部变量(在同一层,如果定义在嵌套内层则还是会隐藏),ANSI C不会隐藏,编译时会出错:
void func(int i ){
    //!int i;//编译出错,因为这两个i在同一层定义
    {
       int i;//这属性嵌套定义,会隐藏外层相同定义
    }
}
 
 
任何在所有代码块之外的标识符都具有文件作用域,它从声明之处到源文件结尾处都是可以访问的。在文件中定义的函数名也具有文件作用域,因为函数名本身并不属于任何代码块
 
原型作用域只适用于在函数原型中声明的参数名,由于原型中,参数名并非必须,这与函数的定义不同,如果出现参数名,你可以随便给它们取任何名字。
 
链接时,如果相同的标识符出现在几个不同的源文件中时,标识符的链接属性决定如何处理在不同文件中出现的相同标识符。标识符的作用域与它的链接属性有关,但这两个属性并不相同。链接属性一共有3种:external(外部)internal(内部)none()。没有链接属性的标识符(none)总是被当作单独的个体,也就是说该标识符的多个声明被当作不同的实体。属于internal链接属性的标识符在同一个源文件内的所有声明中都指同一个实体,但位于不同源文件的多个声明则分属不同的实体。最后,属于external链接属性的标识符不论声明多少次、位于几个源文件都表示同一个实体:
/*1*/typedefchar *a;
/*2*/int b;
/*3*/int c(int d /*4*/) {
    /*5*/int e;
    /*6*/int f(/*7*/int g);
}
在缺省情况下,标识符bcf的链接属性为external(这里的意义应该是可以被外部其他源文件访问,而external关键本身的意义是引用其他源文件中的变量或函数),其余标识符的链接属性则为none。关键字externstatic用于在声明中修改标识符的链接属性,如果某个声明在正常情况下具有external链接属性,在它前面加上static 关键字可以使它的链接属性变为internalstatic只对缺省链接属性为external的声明才有改变链接属性的效果。例如,尽管你可以在声明5前面加上static关键字,但它的效果完全不一样,因为e的缺省链接属性并不是external
 
函数代码就是存储在静态内存中
 
声明3k指定external链接属性,这样函数就可以访问在其他源文件声明的外部变量了:
/*1*/staticint i;
int func() {
    /*2*/int j;
    /*3*/externint k;
    /*4*/externint i;
}
external关键字用于源文件中一个标识符的第一次声明时,它表示该标识符具有external链接属性,但如果它用于该标识符的第二次或以后的声明,它并不会更改由第一次声明所指定的链接属性,上面的声明4并不修改由声明1所指定的变量i的链接属性。
 
凡是在任何代码块之外声明的变量总是存储于静态内存中,不在堆栈内存,这类变量称为静态(static)变量。
在代码块内部声明的变量的缺省存储类型是自动的,也就是说它存储于堆栈中,称为自动(auto)变量。
函数的形式参数不能声明为静态的,因为实参总是在堆栈中传递给函数。
关键字register可以用于自动变量的声明。
 
static
当它用于函数定义时,或用于代码块之外的变量声明时,static关键字用于修改标识符的链接属性,从external改为internal,但标识符的存储类型和作用域不受影响。用这种方式声明的函数或变量只能在声明它们的源文件中访问。
当它用于代码块内部的变量声明时,static关键字用于修改变量的存储类型,从自动变量修改为静态变量,但变量的链接属性和作用域不受影响。该变量在运行期一存在。

第三章     操作符


逻辑右移,左边补零;算术右移,根据左边符号位来决定
 
标准说明无符号值执行的所有移位操作都是逻辑移位,但对于有符号值,是逻辑还是算术取决于编译器,需要写一个程序测试一下。因此,一个程序如果使用了有符号数右移操作,它就是不可移植的。
 
下面这段代码是错误的
char ch;
while((ch=getchar())!=EOF){...}
EOF需要的位数比字符型值所能提供的位数要多,这也是getchar返回一个整型值而不是字符值的原因。然而,把getchar的返回值首先存储于ch中将导致它被截断。然后这个被截断的值被提升为整型并与EOF进行比较。当这段代码在使用有符号字符集的机器上运行时,如果读取了一个值为\377的字节时,循环将会终止,因为这个值截断再提升之后(提升时补符号位)与EOF相等。当这段代码在使用无符号字符集的机器上运行时(提升时补0),这个循环将永远不会终止。
 
由于C语言中的char可以是有符号的,所以与Java是不同的,Java中的字符永远是无符号的,Java中窄的整型转换成较宽的整型时符号扩展规则:如果最初的数值类型是有符号的,那么就执行符号扩展(即如果符号位为1,则扩展为1,如果为零,则扩展为0);如果它是char,那么不管它将要被提升成什么类型,都执行零扩展
 
如果函数f没有副作用,它们是等同的:
a[2 * (y – 6 * f (x) ) ] = a[2 * (y – 6 * f (x) ) ] + 1;
a[2 * (y – 6 * f (x) ) ] += 1;
第一个下标会计算两次,而第二只会计算一次,第二种效率高。
 
sizeof操作符判断它的操作数的类型长度,以字节为单位,操作数既可以是个表达式(常常是单个变量),也可以是两边加上括号的类型名:
    printf("%d\n",sizeof 1);//4
    printf("%d\n",sizeof (1));//表达式两边也可加上括号
    printf("%d\n",sizeof (char));//1
    printf("%d\n",sizeof (short));//2
    printf("%d\n",sizeof (int));//4
    printf("%d\n",sizeof (long));//4
sizeof的操作数是个数组名时,它返回该数组的长度,以字节为单位。
sizeof(a = b +1)并不会向a赋任何值。
 
Java
       int a = 1;
       a = a++;
       System.out.println(a);//1
       int b = 1;
       b = ++b;
       System.out.println(b);//2
C
    int a = 1;
    a = a++;
    printf("%d\n", a);
    int b = 1;
    b = ++b;
    printf("%d\n", b);
 
逗号操作符将两个或多个表达式分隔开来,这些表达式自左向右逐个进行求值,整个逗号表达式的值就是最后那个表达式的值,如:
if(b + 1, c / 2, d > 0);
如果d的值大于0,那么整个表达式的值就为真。
 
下标引用和间接访问表达式是等价的:
arry[下标]
*(arry + (下标))
下标引用实际上是以后面这种形式实现的。
 
.->操作符用于访问一个结构的成员。如果s是个结构变量,那s.a就是访问s中名叫a的成员。当你拥有一个指向结构的指针而不是结构本身,且想访问它的成员时,就需要使用->操作符而不是.操作符。
 
零是假,任何非零值皆为真。如果flag的值只是01,则下面每两对中的两个语句是等价的:
#defined FALSE 0
#defined TRUE 1
...
if(flag == FALSE)...
if(!flag)...
...
if(flag == TRUE)...
if(flag)...
但是,如果flag设置为任意的整型值,那么第2对语句就不是等价的了,可以显示地对它进行测试来解决这个问题:
if(flag != 0)...
if(flag)...
 
*p即可作为左值,也可以作为右值,如:
int a, *p;
*p = 20;
a = *p;
*p=20赋值左边显示是一个表达式,但它却是一个合法的。因为指针p的值是内存中某个特定位置的地址,*操作符使机器指向那个位置。当它作为左值使用时,这个表达式指定需要进行修改的位置,当它作为右值使用时,它就提取当前存储于这个位置的值。
 
    int a = 5000;
    int b = 25;
    long c = a * b;
32位整数的机器上,这段代码运行起来没有问题,但在16位整数的机器上,这个乘法运算会产生溢出。解决方案是将其中任何一个或两个强转为long
    long c = a * (long)b;
 
两个相邻(不相邻则不适用此规则操作符的执行顺序由它们的优先级决定。如果它们的优先级相同,它们的执行顺序由它们的结合性决定。除此之外,编译器可以*决定使用任何顺序对表达式进行求值,只要它不违背逗号、&&||?:操作符所施加的限制。如:a + b * c表达式中,乘法和加法操作符是两个相邻的操作符。由于*操作符的优先级比+操作符高,所以乘法运算先于加法运算执行,编译器在这里别无选择,它必须先执行乘法运算。下面是一个更有趣的表达式:a * b + c * d + e * f,如果仅由优先级决定这个表达式的求值顺序,那么所有3个乘法运算将在所有加法运算之前进行,但事实上不是这样的,实际上只保证每个乘法运算在它相邻的加法运算之前执行即可这个表达式可能按下面顺序进行:
a * b
c * d
(a * b) + (c * d)
e * f
(a * b )+ (c * d) + (e * f)
注意,第1个加法运算在最后一个乘法运算之前执行,但最终的结果是一样的。所以优先级只对相邻操作符的执行顺序起作用,这里并没有任何规则要求所有的乘法运算首先进行,也没有规则规定这几个乘法运算之间谁先执行。
 
f() + g() + h(),尽管左边那个加法运算必须在右边那个加法运算之前执行,但对于各个函数调用的顺序,并没有规则加以限制。为了避免副作用,请使用临时变量先调用。
 
c + --c是危险的:操作符的优先级规则要求自减运算在加法运算之前进行,但我们并没有办法得知加法操作符的左操作数是在右操作数之前还是之后进行求值。它在这个表达式中将存在区别,因为自减操作符具有副作用。--cc之前或之后执行,表达式的结果将不同。每种编译器可能都不同,像这样的表达式是不可移植的,这是由于表达式本身的缺陷所引起的。
 
a=b=10;
c = ++a;// a增加至11c得到的值为11
d = b++;//b增加至11,但d得到的值仍为10
前缀和后缀形式的增值操作符都自制一份变量值的拷贝,用于周围表达式(上面指赋值操作)的值正是这份拷贝。前缀操作符在进行复制之前增加变量的值,后缀操作符在进行复制之后才增加变量的值。这些操作的结果不是被它们所修改的变量的值,而是变量值的拷贝,搞清这一点非常重要。
 
优先级决定表达式中各种不同的运算符起作用的优先次序,而结合性则在相邻的运算符的具有同等优先级时,决定表达式的结合方向。

操作符

描述

结合性

() [] . ->

()函数调用(也可用来改变表达式的优先级)[]数组下标

.通过结构体变量访问成员变量

->通过结构体指针访问成员变量

 

left-to-right

++ -- + - ! ~ (type) * & sizeof

++/后自增

--/后自减

+一元正

-一元负

!逻辑取反/按位取反

(type)类型转换

*间接访问

&:取地址

sizeof确定类型或表达式所在字节数

right-to-left

* / %

//取模

left-to-right

+ -

/

left-to-right

<< >>

左移位,右移位

left-to-right

< <= > >=

小于/小于等于/大于/大于等于

left-to-right

== !=

等于/不等于

left-to-right

&

按位与

left-to-right

^

位异或

left-to-right

|

按位或

left-to-right

&&

逻辑与

left-to-right

||

逻辑或

left-to-right

?:

条件操作符

right-to-left

= += -= *= /= %= &= ^= |= <<= >>=

=赋值

+=加赋值

-=减赋值

*=乘赋值

/=除赋值

%=模赋值

&=位与赋值

^=位异或赋值

|=位或赋值

<<=位左移赋值

>>=位右移赋值

right-to-left

,

逗号

left-to-right

 

说明,同一行的优先级是相同的
 
int main(int argc, char **argv) {
    int i[] = { 3, 5 };
    int *p = i;
    //先拷贝p,再让p下移,再取拷贝p中值,
    //值减一后再拷贝,最后将值拷贝赋给j
    int j = --*p++;
    printf("%d\n", j);//2
    printf("%d", *p);//5
}
 
(一)a = b = c;
关于优先级与结合性的经典示例之一就是上面这个“连续赋值”表达式。
b的两边都是赋值运算,优先级自然相同。而赋值表达式具有“向右结合”的特性,这就决定了这个表达式的语义结构是“a = (b = c)”,而非“(a = b) = c”。即首先完成cb的赋值,然后将表达式“b = c”的值再赋向a
一般来讲,对于二元运算符▽来说,如果它是“向左结合”的,那么“x y z”将被解读为“(x y) z”,反之则被解读为“x (y z)”。注意,相邻的两个运算符可以不同(如: a.b->c表达式则是 ((a.b)->c),但只要有同等优先级,上面的结论就适用。再比如“a * b / c”将被解读为“(a * b) / c”,而不是“a * (b / c)”,这可能导致完全不同的结果。
而一元运算符的结合性问题一般会简单一些,比如“*++p”只可能被解读为“*(++p)”。三元运算符后面会提到。
(二)*p++;
像下面这样实现strcpy函数的示例代码随处都能见到:
char* strcpy(char* dest, constchar* src) {
    char*p = dest;
    while (*p++ = *src++);
    return dest;
}
理解这一实现的关键在于理解“*p++”的含义。
用运算符“*”的优先级低于后自增运算符“++”,所以,这个表达式在语义上等价于“*(p++)”,而不是“(*p)++”。
 
size_t strlen(constchar* str) {
    constchar* p = str;
    while (*p++);
    return p - str - 1;
}
(三)x > y ? 100 : ++y > 2 ? 20 : 30
int x = 3;
int y = 2;
int z = x > y ? 100 : ++y > 2 ? 20 : 30;
这里面是两个条件运算符(?:,也叫“三目运算符”)嵌套,许多人会去查条件运算符的特性,得知它是“向右结合”的,于是认为右侧的内层条件运算“++y > 2 ? 20 : 30先求值,这样y首先被加1,大于2的条件成立,从而使第二个条件运算取得结果“20”;然后再来求值整个条件表达式。这时,由于y已经变成3,“x > y”不再成立。整个结果自然就是刚刚求得的20了。
这种思路是错误的。
错误的原因在于:它把优先级、结合性跟求值次序完全混为一谈了。
首先,多数情况下,C语言对表达式中各子表达式的求值次序并没有严格规定;其次,即使是求值次序确定的场合,也是要先确定了表达式的语义结构,在获得确定的语义之后才谈得上“求值次序”。
对于上面的例子,条件运算符“向右结合”这一特性,并没有决定内层的条件表达式先被求值,而是决定了上面表达式的语义结构等价于x > y ? 100 : (++y > 2 ? 20 : 30)”,而不是等价于“(x > y ? 100 : ++y) > 2 ? 20 : 30。——这才是“向右结合”的真正含义。
编译器确定了表达式的结构之后,就可以准确地为它产生运行时的行为了。条件运算符是C语言中为数不多的对求值次序有明确规定的运算符之一另位还有三位,分别是逻辑与“&&”、逻辑或“||”和逗号运算符“,)。
C语言规定:条件表达式首先对条件部分求值,若条件部分为真,则对问号之后冒号之前的部分求值,并将求得的结果作为整个表达式的结果值,否则对冒号之后的部分求值并作为结果值
因此,对于表达式“x > y ? 100 : (++y > 2 ? 20 : 30)”,首先看x大于y是否成立,在本例中它是成立的,因此整个表达式的值即为100。也因此冒号之后的部分得不到求值机会,它的所有副作用也就没机会起作用。
 
为了可移植性,尽量使用函数库。
 
 
请编写函数:
unsignedint reverse_bits(unsignedint value)
这个函数的返回值把value进行翻转,如在32机器上,25这个数的二进制如下:
00000000 00000000 00000000 00011001
则函数返回的值应该为2550136832,它的二进制位模式为:
10011000 00000000 00000000 00000000
/*
* 将一个无符号整型进行翻转
*/
unsignedint reverse_bits(unsignedint value) {
    unsignedint answer=0;
    unsignedint i;
    /* 使用位移操作来控制次数,这样可以避免不可移植性
     * i不是0就继续进行,这就使用循环与机器字长无
     * 关,从而避免了可移植性问题
     */
    for (i = 1; i != 0; i <<= 1) {
 
       /*
        *把旧的answer左移1位,为下一个位留下空间;
        *如果value的最后一们是1answer就与1进行or操作;
        *然后将value右移一位
        */
       answer <<= 1;
       if (value & 1) {
           answer |= 1;
       }
       value >>= 1;
    }
    return answer;
}
int main(int argc, char **argv) {
    printf("%u",reverse_bits(25));//2550136832
}