196 2 1234.c 0 m 我从来没去过纽约原版这类的呢海立方?,会不会有问题?我以前都??

当前位置: >>
面试中常见的C语言问题
第一节1.1 1.C 语言编程中的几个基本概念#include& &与#include& & #include& &和#include& &有什么区别?这个题目考查大家的基础能力,#include& &用来包含开发环境提供的库, #include& &用来包含.c/.cpp 文件所在目录下的头文件。 注意: 有些开发环境可以在当前目录 下面自动收索(包含子目录) ,有些开发环境需要指定明确的文件路径名。 1.2 switch() 1. switch(c) 语句中 c 可以是 int, long, char, float, unsigned int 类型?其实这个题目很基础,c 应该是整型或者可以隐式转换为整型的数据,很明显不能是实型 (float、double)。所以这个命题是错误的。 1.3 const 1. const 有什么用途?虽然 const 很常用,但是我相信有很多人仍然答不上来。 (1) 欲阻止一个变量被改变,可以使用 const 关键字。在定义该 const 变量时,通常需要对 它进行初 始化,因为以后就没有机会再去改变它了; (2) 对指针来说,可以指定指针本身为 const,也可以指定指针所指的数据为 const,或二者 同时指定为 const; (3) 在一个函数声明中,const 可以修饰形参,表明它是一个输入参数,在函数内部不能改 变其值; (4) 对于类的成员函数,若指定其为 const 类型,则表明其是一个常函数,不能修改类的成 员变量; (5) 对于类的成员函数,有时候必须指定其返回值为 const 类型,以使得其返回值不为“左 值” 。 1.4 #ifndef/#define/#endif 1. 头文件中的 #ifndef/#define/#endif 干什么用?其实#ifndef、#define、#endif 这些在 u-boot、linux 内核文件中经常见到,在这么大型的程序 中大量使用,可见它的作用不可小觑。 这些条件预编译多用于对代码的编译控制, 增加代码的可裁剪性, 通过宏定义可以轻松的对 代码进行裁剪。 #ifndef/#define/#endif 最主要的作用是防止头文件被重复定义。 1.5 1.全局变量和局部变量 全局变量和局部变量在内存中是否有区别?如果有,是什么区别?全局变量储存在静态数据库,局部变量在堆栈。 其实,由于计算机没有通用数据寄存器, 则函数的参数、 局部变量和返回值只能保存在堆栈中。 提示: 局部变量太大可能导致栈溢出, 所以建议把较大数组放在 main 函数外,防止产生栈溢出。 思考:如程序清单 1. 1 所示。会出现怎样的情况? 程序清单 1. 1 大数组放在 main 函数中导致堆栈溢出 int main(int argc, char *argv[]) { int iArray[1024 * 1024]; return 0; } 第二节2.1 1.数据存储与变量变量的声明与定义 如程序清单 2. 1 所示会不会报错?为什么?如果不会报错, 又是输出什么结果?程序清单 2. 1 变量的声明与定义 #include&stdio.h&static int a static int b[] ;;int main( int argc , char *argv[] ) { printf( &%d %d \n& , a , b[0] ) ; return 0 ; }static int static inta=8; b[4] ;这个程序是不会报错的,并且连警告都不会出现。输出的结果是:8 0 static int a ,这句程序是声明全局变量static int b[],这句程序是声明全局数组变量 b,并且是不 完全声明,也就是可以省略数组下标。static int a = 8,这里才是定义全局变量 a,static int b[4], 这里是定义全局变量 b。 2.2 局部变量与全局变量的较量 1. 请问如程序清单 2. 2 所示输出什么? 程序清单 2. 2 局部变量与全局变量 #include&stdio.h&static int a = 8 ; int main( int argc , char *argv[] ) { int a = 4 ;printf( &%d \n& , a ) ;return 0 ; } C 语言规定,局部变量在自己的可见范围内会“挡住”同名的全局变量,让同名的全局变量 临时不可见。 即在局部变量的可见范围内不能访问同名的全局变量。 因此本程序输出为: 4。 2.3 char、int、float、double 的数据存储 1. 请问如程序清单 2. 3 所示,i 和 j 输出什么? 程序清单 2. 3 数据存储 float i = 3 ; int j = *(int*)(&i) ;printf( &i = %f \n& , i ) ; printf( &j = %#x \n& , j ) ; i 是毋庸置疑是:3.000000。但是 j 呢?3.000000?答案是否定的,j 是输出:0x。 有人会问了,难道 j 是随机输出?瞎说,j 输出 0x 是有依据,是一个定值! 由于 i 是 float 数据类型,而 j 是 int 数据类型。理论上说,j 是取了 i 的地址然后再去地址, 应该得到的就是 i 的值:3。但是问题的关键就是 float 数据类型的存储方式和 int 数据类型 不一样,float 是占用 4 个字节(32 位),但是 float 存储是使用科学计数法存储,最高位是存 储数符(负数的数符是 0,正数的数符是 1);接下来 8 位是存储阶码;剩下的 23 位是存储尾 数。上面 i=3.000000,那么 3. 进制) = 11(2 进制) = &v:shape id=_x style=&WIDTH: 40.5 HEIGHT: 21.75pt& equationxml=' 121.1 &21' type=&#_x0000_t75&& (二 进制)。数据在电脑中存储都是二进制,这个应该都没有疑问。那么这里的数符为:0 ,阶 码为:E C 127 = 1 ,那么阶码为:E = 128 即为: (2 进制) ,尾数为:100 00
。那么存储形式就是:00 00 。这 个数据转换成 16 进制就是 0x。图 2. 1 数据存储方式char、int、float、double 的存储方式如图 2. 1 所示。 提问:如果 i = -3.5 的话,请问 j 输出多少? i = -3.500000 j = 0xc0600000 这个希望读者自行分析。 再问:如果如程序清单 2. 4 所示。 程序清单 2. 4 数据存储 double i = 3 ; int j = *(int*)(&i) ;printf( &i = %lf \n& , i ) ; printf( &j = %#x \n& , j ) ; 这样的话,j 又输出多少呢? 提示: double( 8 个字节(64 位) )的存储方式是: 最高位存储数符, 接下来 11 位存储阶码, 剩下 52 位存储尾数。 是不是得不到你想要的结果?double 是 8 个字节,int 是 4 个字节。一定别忘记了这个。用 这个方法也同时可以验证大小端模式! 2.4 容易忽略 char 的范围 1. 如程序清单 2. 5 所示,假设&b=0x12ff54,请问三个输出分别为多少? 程序清单 2. 5 char 的范围unsigned int b = 0x12ff60 ; printf(&( (int)(&b)+1 ) = %#x \n& , ( (int)(&b)+1 ) ); );printf(&*( (int*)( (int)(&b)+1 ) ) = %#x \n& , *( (int*)( (int)(&b)+1 ) )printf(&*( (char*)( (int)(&b)+1 ) ) = %#x \n& , *( (char*)( (int)(&b)+1 ) ) ) ; 很显然,&b 是取无符号整型 b 变量的地址,那么(int)(&b)是强制转换为整型变量,那么加 1 即为 0x12ff54+1=0x12ff55。所以( (int)(&b)+1 )是 0x12ff55。图 2. 3 指针加 1 取字符型数据由于( (int)(&b)+1 )是整型数据类型,通过(int *)( (int)(&b)+1 )转化为了整型指针类型,说明要 占 4 个字节, 即为: 0x12ff55、 0x12ff56、 0x12ff57、 0x12ff58, 再去地址*( (int *)( (int) (&b)+1 ) ) 得到存储在这 4 个字节中的数据。但是很遗憾,0x12ff58 我们并不知道存储的是什么,所以 我们只能写出 0x**0012ff。**表示存储在 0x12ff58 中的数据。如图 2. 2 所示。图 2. 2 指针加 1 取整型数据以此类推,*( (char *)( (int) (&b)+1 ) ) = 0xff。如图 2. 3 所示。 但是,*( (char *)( (int) (&b)+1 ) )输出的却是:0xff ff ff ff ! 问题出现了,为什么*( (char *)( (int) (&b)+1 ) )不是 0xff,而是 0xff ff ff ff?char 型数据应该占 用 1 个字节,为什么会输出 0xff ff ff ff? 使用%d 输出, printf(&*( (char*)( (int)(&b)+1 ) ) = %d \n& , *( (char*)( (int)(&b)+1 ) ) ) ; 结果为-1??? 问题出在 signed char 的范围是:-128~127,这样肯定无法储存 0xff,出现溢出。所以将 printf(&*( (char*)( (int)(&b)+1 ) ) = %#x \n& , *( (char*)( (int)(&b)+1 ) ) ) ; 改成 printf(&*( (unsigned char*)( (int)(&b)+1 ) ) = %#x \n& , *( (unsigned char*)( (int)(&b)+1 ) ) ) ; 就可以输出 0xff,因为 unsigned char 的范围是:0~255(0xff)。 第三节3.1 1.数学算法解决 C 语言问题N!结果中 0 的个数 99!结果中后面有多少个 0?谁跟你说过高数没用?数学是 C 语言的支撑,没有数学建模的支撑就没有那么经典的 C 语 言算法! 如果你能一个一个乘出来我算你狠!我也佩服你! 0 也就意味着乘积是 10 的倍数,有 10 的地方就有 5.有 5 就不担心没 2.10 以内能被 5 整除 的只有 5,但是能被 2 整除多的去了。所以就简化成了这个数只要有一个因子 5 就一定对应 一个 0. 所以可以得出下面结论: 当 0 & n & 5 时,f(n!) = 0; 当 n &= 5 时,f(n!) = k + f(k!), 其中 k = n / 5(取整) 。 如程序清单 3. 1 所示。 程序清单 3. 1 求 N!中 0 的个数 #include&stdio.h& int fun(int iValue) { int iSum = 0; while(iValue / 5 != 0) { iSum iValue } return iS } += (iValue / 5 ); /= 5;int main( int argc , char *argv[] ) { int iNumber, iZoreNscanf( &%d&, &iNumber); iZoreNumber = fun(iNumber); printf( &%d\n&, iZoreNumber);return 0; } 所以 99!后面有多少个 0 的答案是:99 / 5 = 19 , 19 / 5 = 3 ; 3 / 5 = 0 .也就是:19 + 3 + 0 = 22. 这里延伸为 N!呢,一样的,万变不离其宗! 3.2 N!结果中的位数 1. 请问 N!结果中有几位数? 数学!还是数学,数学真的博大精深,如果大学没有好好学数学,会成为你一辈子的遗憾。 我们先假设:N! = 10 ^A,我们知道 10^1~10^2(不含 10^2)之间是 2 位数,10^2~10^3 (不含 10^3)之间是 3 位数,以此类推,10^A~10^(A+1)(不含 10^(A+1))则是(A+1)位 数,那就是说,我们只要求出 A,即可知道 N!有几位数。A=log10(N!), N!= 1*2*3??*N,那么 A= log10(1)+log10(2)+??+log10(N). 这样好计算了吧!程序如程序清单 6. 2 所示。 程序清单 6. 2 求 N!结果中的位数 #include &stdio.h& #include &math.h& int main(int argc, char *argv[]) { int iNumber , i = 0 ; double sum = 0 ; printf(&请输入 iNumber :&); scanf( &%d& , &iNumber ); for( i = 1 ; i & ( iNumber + 1 ) ; i++) { sum += log10(i) ; } printf(& N!有%d 位 \n& , (int)sum + 1 ) ; return 0; } 我们看下调试结果: 请输入 iNumber :10 N!有 7 位 请按任意键继续. . . 网友可以自行验证。第四节1.1 1. static 程序清单 4. 1关键字、运算符与语句如程序清单 4. 1 所示,请问输出 i、j 的结果? static#include &stdio.h&void fun1(void) { static int i = 0 ; i++ ; printf(&i = %d } & , i );void fun2(void) { j=0; j++ ; printf(&j = %d \n& , j ); }int main(int argc, char *argv[]) {int k = 0 ; for( k = 0 ; k & 10 ; k++ ){ fun1() ; fun2() ; printf(&\n&); }return 0; } 答案: i=1 j=1i=2j=1i=3j=1 i=4j=1i=5j=1i=6j=1i=7j=1i=8j=1i=9j=1i = 10j=1请按任意键继续. . .很多人傻了,为什么呢?是啊,为什么呢?! 由于被 static 修饰的变量总存在内存静态区,所以运行这个函数结束,这个静态变量的 值也不会被销毁,函数下次使用的时候仍然能使用这个值。 有人就问啊,为什么 j 一直是 1 啊。因为每次调用 fun2()这个函数,j 都被强行置 0 了。 static 的作用: (1) 函数体内 static 变量的作用范围为该函数体,不同于 auto 变量,该变量的内存只 被分配一次, 因此其值在下次调用时仍维持上次的值; (2) 在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数 访问; (3) 在模块内的 static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被 限制在声明 它的模块内; (4) 在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝; (5) 在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只 能访问类的 static 成员变量。1.2 1.for 循环的深究 如程序清单 4. 2 所示,输出结果是什么? for 循环程序清单 4. 2#include &stdio.h&int main(int argc, char *argv[]) { int i = 0 ;for( i = 0 ,printf(&First = %d & , i ) ; printf(&Second = %d & , i ) , i & 10 ; i++ , printf(&Third = %d & , i )) { printf(&Fourth = %d \n& , i) ; } return 0; } 这个题目主要考察对 for 循环的理解。我们先来看看运行程序会输出什么? First = 0 Third = 1 Third = 2 Third = 3 Third = 4 Third = 5 Third = 6 Third = 7 Third = 8 Third = 9 Second = 0 Fourth = 0Second = 1 Fourth = 1 Second = 2 Fourth = 2 Second = 3 Fourth = 3 Second = 4 Fourth = 4 Second = 5 Fourth = 5 Second = 6 Fourth = 6 Second = 7 Fourth = 7 Second = 8 Fourth = 8 Second = 9 Fourth = 9 请按任意键继续. . .Third = 10 Second = 10从输出我们就可以知道程序到底是什么运行: 首先 i = 0 , 所以输出:First = 0 ; 接着输出:Second = 0 ; i & 10 成立,则输出:Fourth = 0 。就此完成第一个循环。接着 i ++ , 此时 i = 1 ,输出:Third = 1 ;接着输出:Second = 1 ;i & 10 成立,则输出:Fourth = 1 ??? ???以此类推。1.3 尺子――sizeof 1. 如程序清单 4. 3 所示,sizeof(a),sizeof(b)分别是多少? 程序清单 4. 3 sizeof#include &stdio.h& int main(int argc, char *argv[]) { char a[2][3] ;short b[2][3] ; printf( &sizeof(a) = %d \n& , sizeof( a ) ) ; printf( &sizeof(b) = %d \n& , sizeof( b ) ) ; return 0; } 这个题目比较简单,由于 char 是 1 个字节、short 是 2 个字节,所以本题答案是: sizeof(a) = 6 sizeof(b) = 12 请按任意键继续. . .好的,再来看看如程序清单 4. 4 所示,sizeof(a),sizeof(b)分别是多少? 程序清单 4. 4 sizeof#include &stdio.h& int main(int argc, char *argv[]) { char *a[2][3] ;short *b[2][3] ; printf( &sizeof(a) = %d \n& , sizeof( a ) ) ; printf( &sizeof(b) = %d \n& , sizeof( b ) ) ; return 0; } 是数组指针呢, 还是指针数组呢?这里涉及*和[]和优先级的问题。 我告诉大家的是这两 个数组存放的都是指针变量,至于为什么,在后续章节会详细解释,然而指针变量所占的字 节数为 4 字节,所以答案: sizeof(a) = 24 sizeof(b) = 24 请按任意键继续. . .1.4 ++i 和 i++ 1. 或许大家都知道,++i 是先执行 i 自加再赋值,但是 i++是先赋值再自加,但是还有 隐藏在后面的东西呢? int i = 0 ; int iNumber = 0 ; iNumber = (++i) + (++i) + (++i) ; C-Free 编译输出是:7,有的编译器输出是:9。这两个答案都是对的,编译器不同所不 同。7 = 2+2+3;9=3+3+3。区别在于答案是 7 的先执行(++i)+(++i)再执行+(++i),但是答案是 9 的是一起执行。这只是前奏,先看几个让你目瞪口呆的例子。编译环境是 VS2010。 int i = 0 ; int iNumber = 0 ; iNumber = (i++) + (++i) + (++i) ; printf( &iNumber = %d \n& , iNumber ) ; 这里输出是:4!!4 = 1+1+2。 ! int i = 0 ; int iNumber = 0 ; iNumber = (++i) + (i++) + (++i) ; printf( &iNumber = %d \n& , iNumber ) ; 这里输出是:4!!4=1+1+2。 !int i = 0 ; int iNumber = 0 ; iNumber = (++i) + (++i) + (i++) ; printf( &iNumber = %d \n& , iNumber ) ; 这里输出是:6!!6=2+2+2。 !这里至少能说明两个问题,其一,先执行前面两个,再执行第三个;其二,(i++)这个 i 的自加是最后执行! int i = 0 ; int iNumber = 0 ; iNumber = (++i) + (i++) + (++i) + (++i) + (i++) ; printf( &iNumber = %d \n& , iNumber ) ; 这个会是多少?!答案是:10!!10=1+1+2+3+3! ! 不同的编译器或许会存在不同的答案,希望读者自行进行验证。1.5 scanf()函数的输入 1. 如程序清单 4. 5 所示, 运行程序, 当显示 Enter Dividend: , 输入的是 a, 按下 Enter 之后程序会怎么运行? 程序清单 4. 5scanf()函数的输入#include&stdio.h&int main(void) { float fDividend,fDivisor,fRprintf(&Enter Dividend:&); scanf(&%f&,&fDividend);printf(&Enter Divisor:&); scanf(&%f&,&fDivisor);fResult=fDividend/fD printf(&Result is: %f\n&,fResult);return 0; } 这个问题有人会说,肯定是显示 Enter Divisor:要我输入除数咯。是这样吗? 答案是:如果你在 Enter Dividend:之后输入非数字,按下 Enter 之后显示的不是 Enter Divisor: 要你输入除数,而是程序到此就运行结束,显示一个不确定答案,这个答案每一次 都会变。如果你在 Enter Divisor:之后输入非数字,按下 Enter 之后显示的不是 Reslut 的结 果, 而是程序到此就运行结束,显示一个不确定答案,这个答案每一次都会变。 由于 scanf()使用了%f,当输入数字的时候,scanf()将缓冲区中的数字读入 fDividend,并 清空缓冲区。由于我们输入的并非数字,因此 scanf()在读入失败的同时并不会清空缓冲区。 最后的的结果是, 我们不需要再输入其他字符, scanf()每次都会去读取缓冲区, 每次都失败, 每次都不会清空缓冲区。当执行下一条 scanf()函数读取除数时,由于缓冲区中有数据,因此 它不等待用户输入,而是直接从缓冲区中取走数据。 那么防止输入非数字的程序应该怎样呢? #include&stdio.h&int main( int argc , char *argv[] ) { float int char fDividend , fDivisor , fR iR cTmp1[ 256 ] ;printf( &Enter Dividend \n& ) ; iRet = scanf( &%f& , &fDividend ) ;if ( 1 == iRet ) { printf( &Enter Divisor \n& ) ; iRet = scanf( &%f& , &fDivisor ) ; if ( 1== iRet ) { fResult = fDividend / fD printf( &Result is %f \n& , fResult ) ; } else { printf( &Input error ,not a number! \n& ) ; gets(cTmp1) ; return 1 ; } } else { printf( &Input error , not a number! \n& ) ; gets(cTmp1) ; return 1 ; }return 0 ; }1.6 1.scanf()函数的返回值 如程序清单 4. 6 所示,请问输出会是什么? scanf()函数的返回值程序清单 4. 6 int a ,printf( &%d \n& , scanf(&%d%d& , &a , &b) ) ; 输出输入这个函数的返回值?!答案是:2。只要你合法输入,不管你输入什么,输出 都是 2。那么我们就要深入解析 scanf 这个函数。scanf()的返回值是成功赋值的变量数量。1.7 1.const 作用下的变量 阅读如程序清单 4. 7 所示,想想会输出什么?为什么? const 作用下的变量程序清单 4. 7 const intiNumber = 10 ; \n& , iNumber) ;printf(& iNumber = %dint *ptr = (int *)(&iNumber) ; *ptr = 100 ; printf(& iNumber = %d \n& , iNumber) ;const 的作用在第四章已经详细讲了,这里就不再累赘,答案是:10,10。这里补充一个 知识点: const int *p int* const p const int* const p 指针变量 p 可变,而 p 指向的数据元素不能变 指针变量 p 不可变,而 p 指向的数据元素可变 指针变量 p 不可变,而 p 指向的数据元素亦不能变1.8 1.*ptr++、*(ptr++),*++ptr、*(++ptr),++(*ptr)、(*ptr)++的纠缠不清 如程序清单 4. 8 所示程序,输出什么? *ptr++程序清单 4. 8int iArray[3] = { 1 , 11 , 22} ; int *ptr = iAprintf( &*ptr++ = %d \n& , *ptr++ ) ; printf( &*ptr = %d \n& , *ptr ); 纠结啊,是先算*ptr 还是 ptr++;还是纠结啊,ptr 是地址加 1 还是偏移一个数组元素! 这里考查了两个知识点,其一:*与++的优先级问题;其二,数组 i++和++i 的问题。* 和++都是优先级为 2,且都是单目运算符,自右向左结合。所以这里的*ptr++和*(ptr++)是等 效的。 首先 ptr 是数组首元素的地址,所以 ptr++是偏移一个数组元素的地址。那么 ptr++运算 完成之后,此时的 ptr 是指向 iArray[1],所以第二个输出*ptr = 11 。如图 4. 1 所示。那么倒 回来看第一个输出,ptr++是在执行完成*ptr++之后再执行的,所以,*ptr++ = 1 。图 4. 1 ptr++如程序清单 4. 9 所示程序,输出会是什么? 程序清单 4. 9 *++ptrint iArray[3] = { 1 , 11 , 22} ; int *ptr = iAprintf( &*++ptr = %d \n& , *++ptr ) ; printf( &*ptr = %d \n& , *ptr ); 这个解释和上面解释差不多, 就是++ptr 和 ptr++的差别, 所以这里的两个输出都是: 11。 同样的道理,*++ptr 和*(++ptr)是等效。再如程序清单 4. 10 所示,输出又会是什么? 程序清单 4. 10 (*ptr)++ int iArray[3] = { 1 , 11 , 22} ; int *ptr = iAprintf( &(*ptr)++ = %d \n& , (*ptr)++ ) ; printf( &*ptr = %d \n& , *ptr );这个的输出是:1,2。原因请读者分析。第五节C 语言中的细节1.1 “零值”比较 1. 写出 float x 与“零值”比较的 if 语句。 首先要知道 float 是有精度的,不能直接与 0 相比较或者两数相减与 0 相比较。float 能 保留几位小数?答案是 6 位。既然如此,那么就应该这么写: if((x & 0.000001) && (x & -0.000001)) 。1.2 宏定义 1. 定义一个宏,返回 X、Y 中的较大值。 这个题目其实很简单,但是在很多笔试中都会拿出来考试,并且出错率很高,原因只有 一个,忽略细节(优先级的问题,实在搞不明白就加括号吧,你好理解,别人一看也懂) 。 终究还是细节决定成败。 #define MAX( (X) , (Y) ) ((X) &= (Y) ? (X) : (Y))2.宏定义两个数相加请问如程序清单 5. 1 输出什么? 程序清单 5. 1 宏定义两数相加 #define DOUBLE(x) x+xint main(int argc, char* argv[]) { int iNumber = 0 ; printf(&%d\n& , 10*DOUBLE(10)); return 0; } 其实这个程序非常简单, 学习 C 语言一周就应该理解是什么意思, 但是一般会出错的的 地方都在细节。其实这个程序输出是 110。 可能有人会问, 不是 10 先 DOUBLE 嘛, 然后乘以 10, 不是 200 嘛。 是啊, 想法是好的, 我想这个程序的“原意”也应该是这样,但是就是由于优先级的问题,打破了我们的愿望。 如果要得到 200,那么就应该是这样宏定义:#define DOUBLE(x) ((x)+(x))。我想,无论我 加多少层括号都不算违法吧。 1.3 1.递归运算 如程序清单 5. 2 所示,输出什么?程序清单 5. 2 递归运算 #include &stdio.h& int func(int a) { if (a==0) { } printf(&%d\n&,func(a++/2)); }int main(int argc, char *argv[]) { printf(&%d&,func(7)); return 0; } 答案:0,2,4,8 这里把 7 送进去,那么 func(a++/2),先执行 7/2=3,然后 a++ = 8,此时返回 3;接着把 3 送进去, func(a++/2), 先执行 3/2=1, 然后 a++ = 4, 此时返回 1; 接着把 1 送进去, func(a++/2), 先执行 1/2=0,然后 a++ = 2,此时返回 0;接着把 0 送进去,此时直接返回 0,递归结束。 递归最容易忽略的细节是,由于递归次数过多,容易导致堆栈溢出。 1.4 1.让人忽略的贪心法 如程序清单 5. 3 所示,程序输出什么?程序清单 5. 3 贪心法 int k = 8 ; int i = 10 ; int j = 10 ; k *= i+++ printf(&%d \n& , k) ; 贪心法,就是一次性能尽可能多得吃运算符,那么这里 k *= i+++j ,加上括号之后就是这 样:k = k * ((i++) + j) ;这样的话就很简单可以得出答案为:160。1.5 性能优化 1. 对如程序清单 5. 4 所示进行性能优化,使得效率提高。 程序清单 5. 4 性能优化 int iValue1; int iValue2;iValue1 = 1234/16; iValue2 = 1234%32; 对于嵌入式进行除法是很消耗效率的,能使用移位完成最好使用移位完成。 iValue1 = 1234 && 4; iValue2 = 1234 C ((1234 && 5) && 5); 1234 / 16 = 77; 1234 % 32 = 18。 而十进制: 1234 转化成二进制: 10。 1234 && 4 = 01,转化为十进制即为:77;1234 && 5 = 10,((1234 && 5) && 5) 即为 00,转化为十进制即为: C 1216 = 18。第六节1.1 1.数组与指针数组、数组元素、指针的大小 如程序清单 6. 1 所示,程序输出什么?程序清单 6. 1 数组、数组元素、指针的大小 #include &stdio.h& int main(int argc, char *argv[]) { int * printf( &sizeof(p) printf( &sizeof(*p) = %d \n& , sizeof(p) );= %d \n& , sizeof(*p) ) ;int a[100]; printf( &sizeof(a) = %d \n& , sizeof(a) );printf( &sizeof(a[100]) = %d \n& , sizeof(a[100]) ) ; printf( &sizeof(&a) = %d \n& , sizeof(&a) );printf( &sizeof(&a[0]) = %d \n& , sizeof(&a[0]) ) ;return 0; } p 是指针,任何数据类型的指针都是占 4 字节; *p 是一个指针指向的 int 数据,int 型数据占用 4 字节; a 是数组,除了 sizeof(a)和&a 之外,当 a 出现在表达式中时,编译器会将 a 生成一个指 向 a[0]的指针,而这里测量的是整个数组的大小。 答案: sizeof(p) sizeof(*p) sizeof(a) =4 =4 = 400sizeof(a[100]) = 4 sizeof(&a) sizeof(&a[0]) =4 =4请按任意键继续. . . 1.2 广东省的省政府和广州市的市政府都在广州 1. 如程序清单 6. 2 所示, 如果 ptr = 0x , 那么剩下三个输出是多少? 程序清单 6. 2 数组首地址与数组首元素地址 int iArray[3] = { 1 , 2 , 3 } ; int *ptr = iAprintf(&ptr printf(&iArray printf(&&iArray printf(&&iArray[0]= %#x\n& , ptr ) ; = %#x\n& , iArray ) ; = %#x\n& , &iArray ) ; = %#x\n& , &iArray[0] ) ;iArray 是数组名, 6.1 节对 a 的说明, iArray 知同时也是数组首元素的地址, 0x1000 由 可 为 0000; &iArray 是数组的首地址, 这是毫无疑问的。 &iArray[0]是数组首元素的地址, 也是 0x。 也就是说数组的首地址和数组首元素的地址是相等的。 因为广东省的省政府和广东省 的首号城市广州市的市政府都在广州,两者的地址是相等的。如图 6. 1 所示。 如程序清单 6. 3 所示,ptr = 0x ,那么这三个输出会是多少? 程序清单 9. 3 数组首地址加 1、数组首元素地址加 1 int iArray[3] = { 1 , 2 , 3 } ; int *ptr = iAprintf(&&iArray+1 printf(&iArray+1= %#x\n& , &iArray+1 ) ; = %#x\n& , iArray+1 ) ;printf(&&iArray[0]+1 = %#x\n& , &iArray[0]+1 ) ; 答案是,第一个输出:0xC;第二个、第三个输出:0x。 &iArray 是数组的首地址,那么&iArray+1 是偏移一个数组单元,因为站在全国的角度报 全国各省政府的天气预报,报完广东省省政府之后就为湖南省省政府;如图 6. 1 所示。 &iArray[0]是数组首元素的地址,&iArray[0]+1 是偏移一个数组元素单元,好比站在广东的角 度报广东各城市的天气预报, 报完广东省首号城市广州的天气预报之后就是为广东省第二号 城市的天气预报。 1.3 数组作为函数参数,是传什么进去了 1. 如程序清单 6. 4 所示,程序输出什么? 程序清单 6. 4 数组作为函数参数 void text(char cArray[]) { printf( &sizeof(cArray) = %d \n& , sizeof(cArray) ) ; } int main(int argc, char* argv[]) { char cArray[] = &aBcDeF& ; printf( &sizeof(cArray) = %d \n& , sizeof(cArray) ) ; text(cArray) ; return 0; } 这里考查两个知识点, 其一, sizeof 和 strlen(); 其二 text(char cArray[])形参到底是什么? 答案是 7,4。看到答案我想大家就应该明白上面两个问题了吧。到底是传值还是传址一 定要搞明白哦。 1.4 指针相减 1. 如程序清单 6. 5 程序,输出会是什么? 程序清单 6. 5 指针相减 #include &stdio.h& int main(int argc, char *argv[]) { int a[2] = { 3 , 6 } ; int *p int *q =a; =p+1;printf( &q - p = %d \n& , q-p ) ; printf( &(int)q - (int)p = %d \n& , (int)q-(int)p ) ;return 0; } 用数学方法到可以做出 q-p = 1 这个答案, 但是(int)q - (int)p 的答案。 指针, 指针的强大。 由于指针加 1,内存地址是加 sizeof(int),但是 int(q)和 int(p)就不再是指针,而是一个整 形数据。所以(int)q - (int)p = 4 。 1.5 指针加 1 到底是加什么 1.如程序清单 6. 6 所示,请问 p1+5=__;p2+5=__;程序清单 6. 6 指针加 1 #include &stdio.h&int main(int argc, char *argv[]) { unsigned char *p1 ; unsigned long *p2 ;p1 = (unsigned char *)0x801000 ; p2 = (unsigned long *)0x810000 ;printf( &p1+5 = %#x \n& , p1 + 5 ) ; printf( &p2+5 = %#x \n& , p2 + 5 ) ;return 0; } 由于 p + n = p + n * sizeof(p 的数据类型) ,所以答案为: p1+5 = 0x+5 = 0x810014 请按任意键继续. . . 1.6 数组与指针的概念 1. 如程序清单 6. 7 所示,解释程序。 程序清单 6. 7 数组与指针的概念 a) b) int *a; c) int **a; d) int a[10]; e) int *a[10]; f) int (*a)[10]; g) int (*a)(int); h) int (*a[10])(int); 答案: a) b) c) d) e) f) g) 一个整型数 ; 一个指向整型数的指针; 一个指向指针的指针,它指向的指针是指向一个整型数; 一个有 10 个整型数的数组; 一个有 10 个指针的数组,该指针是指向一个整型数的; 一个指向有 10 个整型数数组的指针; 一个指向函数的指针,该函数有一个整型参数并返回一个整型数;h) 一个有 10 个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一 个整型数。 这个题目很经典,很多公司的笔试题目都会截取上面部分出来考试。特别是 e 和 f,哪一 个是数组指针,哪一个又是指针数组;g 和 h 哪一个是函数指针,哪一个又是指针函数。 1.7 数组与指针的糅合 1. 如程序清单 6. 8 所示,输出会是什么? 程序清单 6. 8 数组与指针的糅合应用 1 int arr[] ={6,7,8,9,10}; int *ptr *(ptr++) = +=123;printf("%d,%d",*ptr,*(++ptr)); 这个题目来源于华为的一道 C 语言笔试题,答案是:8,8。*ptr = arr ,这里 ptr 取得是数 组 arr[]的首元素地址, *(ptr++) +=123 , 这里是 ptr++,此时* ptr) ( 即为:6, 那么*(prt++)+123=129, 执行完*(ptr++)+=123 之后,*(ptr) = 7。跟*(++ptr)之后为 8 这个值是没有半点关系,由于执 行了*(++ptr),所以此刻的*(ptr)为 8,所以输出为:8,8。 2. 如程序清单 6. 9 所示,输出会是什么?程序清单 6. 9 数组与指针的糅合应用 2 int main( int argc , char *argv[] ) { int a[5] = { 1 , 2 , 3 , 4 , 5 }; int *ptr = (int *)( &a + 1 );printf( &%d,%d& , *(a+1) , *(ptr-1) ); } 这个题目要求对指针的理解要比较透彻。 由于*(a+1)和 a[1]是等效的, 则*(a+1)=a[1] = 2 。 &a 指的是指向整个数组变量的指针,&a+1 不是首地址+1,系统会认为加了一个 a 数组的偏 移量, 即偏移了一个数组的大小, 因此 ptr 指向的是 a[5],即是*(ptr+5), 既然如此, 那么*(ptr-1) 当然就是 a[4] = 5 咯。所以这个题目的答案是: 2 , 5 。 其实这个题目还有一个延伸, *ptr = (int *)( (int) a + 1 ), *ptr 是多少。 int 答案是:2 00 00 00。 假设 &a[0] = 0x, 由于存储方式是小端模式, 那么 a[0] = 1 和 a[1] = 2 的存储方 式如图 6. 2 所示。 因为 a = 0x, 而(int)a 将这个地址强制转化为了 int 型数据, ((int)a + 1) = 0x,经过(int *)((int)a + 1)成了地址,ptr = (int *)((int)a + 1),由于 ptr 是指向 int 型的指针, *ptr 占 4 个字节,*ptr 所占字节即为:0x00,0x00,0x00,0x02,那么*ptr 即为 0x。 3. 少?如程序清单 6. 10 所示,如果 ptr1 为 0x,那么三个输出分别为多程序清单 9. 10 数组与指针的糅合应用 3 int iArray[7] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 } ; int *ptr1 int *ptr2 = iA = &iArray[5] ;printf( & ptr2 printf( & ptr1= %#x \n& , ptr2 ) ; = %#x \n& , ptr1 ) ;printf( &ptr2 - ptr1 = %d \n& , ptr2 - ptr1 ) ; 很明显 iArray 是整型数据类型数组,那么 ptr2 = ptr1 + 5*sizeof(int) = 0x。很多 同学立马就会脱口而出 ptr2 C ptr1 = 20 嘛!真的是这样吗?其实答案是:5! 解释之前,我们先来看这个程序: int iArray[7] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 } ; char *p1 char *p2 = (char *) iA = (char *) &iArray[5] ;printf( &p2 - p1= %d \n& , p2 - p1 ) ;这个程序的输出是:20。因为指针类型是 char*,char 是 1 个字节;而上面*ptr1 和*ptr2 是 int*,所以答案是:5。 如果是: short *p1 = (short *) iA short *p2= (short *) &iArray[5] ;则 p2 C p1 就是为:10。 这里还有一个延伸: int iArray[7] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 } ; int *ptr1 int *ptr2 = iA = &iArray[5] ;printf( & ptr2 printf( & ptr1= %#x \n& , ptr2 ) ; = %#x \n& , ptr1 ) ;printf( &ptr2 - ptr1 = %d \n& , (int)ptr2 C (int)ptr1 ) ; 这样输出答案是多少呢?20! 1.8 指针数组 1. 阅读程序,输出什么? char *cArray[3] = { &abcdef& , &123456& , &jxlgdx& } ; printf( &sizeof(cArray[0]) = %d \n& , sizeof(cArray[0]) ); 我相信有较多的人的答案是:6 或者 7。原因是忽略了*cArray[3]这是一个指针数组,也 就是说 cArray[3]数组中存放的是指针, 而不是字符串常量。 C 语言笔试陷阱与难点第一阶 在 段讲过,只要是指针变量,其大小就是:4。所以这里毋庸置疑,输出应该是:4。 你要是存在怀疑,可以输出 cArray[3]数组的各个元素看看是不是指针。 printf( &cArray[0] = %#x \n& , cArray[0] ) ; printf( &cArray[1] = %#x \n& , cArray[1] ) ; printf( &cArray[2] = %#x \n& , cArray[2] ) ; 运行程序输出为: sizeof(cArray[0]) = 4 cArray[0] = 0x415840 cArray[1] = 0x415770 cArray[2] = 0x415768 请按任意键继续. . . 读者亦可输出指针所指向的字符串: printf( &cArray[0] = %s \n& , cArray[0] ) ; printf( &cArray[1] = %s \n& , cArray[1] ) ; printf( &cArray[2] = %s \n& , cArray[2] ) ; 运行输出为: cArray[0] = abcdef cArray[1] = 123456 cArray[2] = jxlgdx 请按任意键继续. . . 2. 阅读下列程序,输出什么?typedef int (init_fnc_t) (void);extern int arch_cpu_init(void); extern int board_early_init_f(void);init_fnc_t *init_sequence[] = { arch_cpu_init, board_early_init_f, NULL, };int arch_cpu_init(void) { printf(&This is arch_cpu_init \n&); return 0; }int board_early_init_f(void) { printf(&This is board_early_init_f \n&); return 0; }void hang (void) { printf(&Error! \n&); while (1) ; } int main(int argc, char* argv[]) { init_fnc_t **init_fnc_for (init_fnc_ptr = init_ *init_fnc_ ++init_fnc_ptr) { if ( (*init_fnc_ptr)() != 0 ) { hang (); } }return 0; } 这个题目是我在阅读 u-boot-2012.10 源码的时候稍作修改从中提取出来的。 这个题目将 指针数组、函数指针等知识点融为一体。 This is arch_cpu_init This is board_early_init_f 请按任意键继续. . . 1.9 数组指针 1. 如程序清单 6. 11 所示,程序输出什么? 程序清单 6. 11 数组指针 #include &stdio.h& int main(int argc, char *argv[]) { int a[][4]={ 1,3,5,7,9,11,13,15,17,19,21,23}; int (*p)[4] , i=2 , j=1 ;p=a; printf( &%d\n&, *(*(p+i)+j)); return 0; } 答案是:19。 不能理解?好吧,如果我告诉你**(p+1) = 9, *((*p)+1) = 3!到这里能理解了吗?如果还 是不能理解,ok,p 是指向一个含有 4 个整型数据的数组,那么 p 如果加 1,地址是不是得 偏移 4 个整形数据的地址,而 p 等于数组 a 的首元素地址,a 是二维数组,也就意味着 p 是 双重指针了。 1.10 再论数组指针与数组首地址 1. 如程序清单 6. 12 所示,已知&a[0][0] = 0x22fe70,想想会是输出什么? 程序清单 6. 12 数组指针与数组首地址 int main(int argc, char *argv[]) { int a[8][8] = {1,2,3,4};int (*ptr1)[8]=a;int (*ptr2)[8][8] = &a; int *ptr3 = &a[0][0]; printf(& ptr1 printf(& &a[0] printf(& ptr1+1= %#x\n& , ptr1); = %#x\n& , &a[0]); = %#x\n& , ptr1+1);printf(& &a[0]+1 = %#x\n\n& , &a[0]+1);printf(& ptr2 printf(& &a printf(& ptr2+1 printf(& &a+1= %#x\n& , ptr2); = %#x\n& , &a); = %#x\n& , ptr2+1); = %#x\n\n& , &a+1);printf(& ptr3 printf(& &a[0][0] printf(& ptr3+1= %#x\n& , ptr3); = %#x\n& , &a[0][0]); = %#x\n& , ptr3+1);printf(& &a[0][0]+1 = %#x\n\n& , &a[0][0]+1);return 0; } 这个题目涉及到两个知识点,其一,讲烂了的数组首元素地址和数组的首地址;其二, 数组指针和指针数组的区别。 先看第三个指针, *ptr3 = &a[0][0];这个毫无疑问, int 是将数组 a 的首元素地址赋给指针 ptr3,由于是 int 型数组,那么 ptr3+1 则是偏移一个 int 型大小,即偏移 4 个字节,那么 ptr3 这一组的输出即为: ptr3 &a[0][0] = 0x22fe70 = 0x22fe70 ptr3+1= 0x22fe74&a[0][0]+1 = 0x22fe74 我们再看第二个指针,int (*ptr2)[8][8] = &a;根据之前我们讲过的,这个是取数组 a 的 首地址,ptr2 的解释就是:一个指向二维数组[8][8]的指针,那么 ptr2+1 则是偏移了一个二 维数组[8][8]的地址,即为 4*8*8=256(0x100)个字节的偏移。那么 ptr2 这一组的输出即为: ptr2 &a ptr2+1 &a+1 = 0x22fe70 = 0x22fe70 = 0x22ff70 = 0x22ff70剩下第一个指针, 这个和 6.9 节差不多, (*ptr1)[8] int =其实它是等价于 int (*ptr1)[8] = &a[8] ; 那 么 ptr1 则 是 一 个 指 向 一 维 数 组 [8] 的 指 针 , 如 果 我 们 这 么 理 解 a[8][8] = {a1[8],a2[8],?a8[8]}(当然这个理解是错误的),那么 ptr1 就是指向 a1[8],那么当 ptr1+1 就是 指向 a2[8], 也就是偏移了一个含有 8 个 int 型数据的数组, 4*8=32(0x20)个字节。 即 那么 ptr1 这一组的输出即为: ptr1 &a[0] ptr1+1 = 0x22fe70 = 0x22fe70 = 0x22fe90&a[0]+1 = 0x22fe90 这里再一次重复讲一下数组指针和指针数组。 int (*p)[8] int *p[8] p 是指向一个含有 8 个整型数据的数组的指针(数组指针) p 是一个含有 8 个指针型变量的数组(指针数组)
第七节结构体与联合体1.1 结构体内存对齐问题 1. 这个程序本是我写来验证结构体内存对齐问题,但是我在 linux 系统和 windows 系统下的答案让我有点意外,我便将其加进本书。如程序清单 7. 1 所示,程序输出 会是什么? 程序清单 7. 1 结构体的内存对齐问题 #include&stdio.h&struct Date{ int int int }; ; ;struct DateType{ int int }month day; ;struct student{ int char iNum cName[30] ; ; ; ; ;float fScore char cSexdouble menber }int main( int argc , char *argv[] ) { printf( &sizeof(struct Date) sizeof( struct Date) ); = %d \n\n&,printf( &sizeof(struct DateType) = %d \n& , sizeof( struct DateType) ) ; printf( &sizeof(birthday) printf( &&birthday.year printf( &&sizeof.month printf( &&sizeof.day = %d \n\n&, sizeof( birthday ) = %d \n& , &birthday.year = %d \n& , &birthday.month = %d \n\n&, &birthday.day ); ); ); ); printf( &sizeof(struct student) sizeof( struct student) ) ; printf( &sizeof(people) printf( &&people.iNum printf( &&people.cName printf( &&people.fScore printf( &&people.cSex &people.cSex );= %d \n& ,= %d \n\n&, sizeof( people ) = %d \n& , &people.iNum = %d \n& , &people.cName = %d \n& , &people.fScore = %d \n& ,); ); ); );printf( &&people.menber printf( &sizeof(people.menber)= %d \n\n&, &people.menber = %d \n\n&, sizeof( people.menber ) ) ;);return 0 ; } 传统在 windows 下,结果大家都应该知道,我现在就直接把 window 下和 linux 下结果 直接贴出来,大家看看。 (如果大家对结果有质疑,大可上机试试,毕竟眼见为实。 ) sizeof(struct Date) = 12sizeof(struct DateType) = 12 sizeof(birthday) = 12&birthday.year= 4210832 &sizeof.month &sizeof.day= 4210836 = 4210840sizeof(struct student) = 56 sizeof(people) = 56&people.iNum &people.cName &people.fScore &people.cSex &people.menber= 4210848 = 4210852 = 4210884 = 4210888 = 4210896sizeof(people.menber)=8请按任意键继续. . . 上面是 C-Free 中运行的结果,你可以试试 VC 等,答案依然如此。我们再来看看 linux 下结果: root@zhuzhaoqi-desktop:/home/zhuzhaoqi/C/prog1.34# ./prog sizeof(struct Date) = 12sizeof(struct DateType) = 12 sizeof(birthday)= 12&birthday.year &sizeof.month &sizeof.day=
= sizeof(struct student) = 52 sizeof(people) = 52&people.iNum &people.cName &people.fScore &people.cSex &people.menber=
= sizeof(people.menber)=8这是 linux 下编译的结果。 加粗标注区域够让你吃惊吧! 说实话,看到第一眼,我也傻了。为什么,我们再看下划线标注区域,people.cSex 在 windows 下联系上下确实应该占用 8 个字节, 可是, 可是为什么在 linux 下只占用 4 个字节! ! 原来, linux 中以 4 个字节为开辟单元,即不足 4 个开辟 4 个,多于 4 个的继续开辟 4 个, 在 多出的部分 放进另一个 4 个字节中。 struct student{ int chariNum;/* 开辟 4 个字节 */cName[30] ; /* 开辟 32 个字节 */ ; /* 开辟 4 个字节 */float fScore/*开辟 4 个字节,自己用 1 个字节,剩下 3 个,不足以存储 menber */ char cS ; /* 所以这里重新开辟 4+4 个字节 */double menber }所以我们得出的答案是:4+32+4+4+8=52。 但是,我们一直使用的 windows 下,以最大单元为开辟单位,即系统先检查结构中最大 单位 为 double 8 个字节,所以以 8 个字节为单位。 student 在 Linux 和 windows 下内存开辟如图 7. 1 和图 7. 2 所示。1.1 1. 什么?结构体在 STM32 的应用 如程序清单 7. 2 所示程序是截取 STM32 固件库中的一段代码,请问输出是程序清单 7. 2 结构体在 STM32 的应用 #include &stdio.h& typedef volatile unsigned int vui32;typedef struct { vui32 CRL; vui32 CRH; vui32 IDR; vui32 ODR; vui32 BSRR; vui32 BRR; vui32 LCKR; } GPIO_TypeD#define GPIOA #define GPIOLED(GPIO_TypeDef *)0x (GPIO_TypeDef *)GPIOAvoid func (GPIO_TypeDef *GPIO) { printf(&GPIO-&CRL = %#x\n& , &(GPIO-&CRL)); printf(&GPIO-&CRH = %#x\n& , &(GPIO-&CRH)); printf(&GPIO-&LCKR = %#x\n& , &(GPIO-&LCKR)); }int main(int argc, char *argv[]) { printf(&sizeof(GPIO_TypeDef) = %d\n& , sizeof(GPIO_TypeDef)) ; printf(&GPIOLED=%#x\n& , GPIOLED); func(GPIOLED); return 0; } 如果使用过 STM32 的固件函数库的话,应该对这个结构体不会陌生,STM32 固件函数 库就是这样,通过“大行其肆”的结构体和指针实现对一大堆寄存器的配置,在_map.h 这 个头文件中,定义很多这样的结构体。这样做有什么好处呢,将共同点给抽象出来了,上面 7 个寄存器就是每个 GPIO 口寄存器的共有特性,那么只要给定某一个 GPIO 口的映射地址, 很快就可以通过这个结构体得到每个寄存器的地址。 能这么做很巧的是 ARM 的 MCU 每个寄 存器的偏移量都是 4 个字节或者 2 个字节,所以能使用结构体完成,如果有一天出现了 3 个字节的偏移量,我想此时结构体也就没辙了。 答案是: sizeof(GPIO_TypeDef) = 28 GPIOLED =0xGPIO-&CRL = 0x GPIO-&CRH = 0xGPIO-&LCKR = 0x 请按任意键继续. . . 确实很巧妙,方便!1.2 1.结构体与指针 已知如下所示条件。struct student{long int charnum *name; ; ; ; ;short int date char sexshort int da[5] }*p; p = (student*)0x1000000 ; 那么请问,以下输出什么? printf( &sizeof(*p) = %d\n& , sizeof(*p) ); ); ); ); ); );printf( &sizeof(student) = %d\n& , sizeof(student) printf( &p printf( &p + 0x200 printf( &(char*)p + 0x200 printf( &(int*)p + 0x200 = %#x\n& , p = %#x\n& , p + 0x200= %#x\n& , (char*)p + 0x200 = %#x\n& , (int*)p + 0x200第一个输出不解释,内存对齐问题,结构体指针,答案为:24。 第二个输出答案为:24。 第三个输出,为已知,答案为:0x1000000。 第四个输出,由于 p 此时是结构体类型指针,那么 p+0x200 = p + 0x200*sizeof(student)。 所以 p + 0x200 = p + 0x200 * 24 = 0x1000000 + 0x3000 = 0x1003000。 第五个输出,由于 p 被强制转换成了字符型指针,那么 p + 0x200 = 0x1000200。 第六个输出同理为:p + 0x200 = 0x1000800。1.3 1.联合体的存储 如程序清单 7. 3 所示,程序输出什么?程序清单 7. 3 联合体的存储 union { struct { char L; char H; }B }N;int main(int argc, char* argv[]) { N.i = 0x1234; printf(&N.Bity.L = %#x\n&,N.Bity.L); printf(&N.Bity.H = %#x\n&,N.Bity.H); return 0; }结构体的成员是共用一块内存,也就是说 N.i 和 N.Bity 是在同一个地址空间中。那么好 办了,但是要注意,CPU 是小段存储模式,所以低字节存储在低地址中,高字节存储在高地 址中。那么 N.Bity.L 是取了低地址,也就是得到低字节,即为 0x34,N.Bity.H 是取了高字节, 即为 0x12。在电脑中,int 是占 4 字节,所以存储方式如图 10. 3 所示。其实这里有一个很巧妙的用法可以用于 C51 单片机中,为了与上面不重复,假设 C51 的存储模式是大端模式。在 C51 的定时器,给定时器赋初值的时候,要将高八位和低八位分 别赋给模式 1 定时器的高位预置值和低位预置值,有这么个式子: THx = ()/256; TLx = ()%256. 那么我们就可以让这样写这个程序 union { struct { unsigned char H; unsigned char L; }B }N;int main(int argc, char* argv[]) { …… N.i = 65536 - 10000; THx = N.Bity.H ; TLx = N.Bity.L ; ?? return 0; } 这样很方便并且高效地将高低位置好, 其实单片机是一个很好学习 C 语言的载体, 麻雀 虽小,但是五脏俱全。 65536 C 10000 = 55536 = 0xD8F0。 由于在单片机中,int 是占 2 字节,那么存储方式如图 7. 4 所示。 1.1 结构体在联合体中的声明 1. 如程序清单 7. 4 所示,请问:printf( &%d\n& , sizeof(T.N) ) ; printf( &%d\n& , sizeof(T) ) ;输出什么?程序清单 7. 4 结构体在联合体中的声明 union T { struct N { }; }; 第一个结构体嘛,4+4+8=16;第二个嘛,最大元素所占内存开辟,则为:16!真的是这 样吗?正确答案是:16,4! union T { struct N { }A; }; 如果这个程序是这样,输出又是多少呢?正确答案是:16,16!不知道你是否知道其中 的原因了呢? 1.2 结构体与联合体的内存 1. 如程序清单 7. 5 所示,语句 printf(&%d&,sizeof(struct date)+sizeof(max));的执行结果 是? 程序清单 7. 5 结构体与联合的内存 typedef union { int k[5]; } DATE;struct data { int DATE }DATE 很明显,这里考查的是联合体和结构体的内存对齐问题。联合体的内存大小是以最大类 型存储内存作为依据,而结构体则是内存对齐相加。 上面例子的联合体最大是:20,所以 sizeof(max) = 20 ;在结构体中,sizeof(cat) = 4 ,sizeof(cow) = 20 ,sizeof(dog) = 8 ,由于内存都是对齐的,所以 siezof(struct date) = 32 .所以 最终答案是:52. #include &stdio.h& typedef union {long int i int char; k[5] ;}DATE ;struct data {int DATE}int main(int argc, char *argv[]) {DATE printf(&sizeof(cat)=%d\n&, printf(&sizeof(cow)=%d\n&,sizeof(too.cat)); sizeof(too.cow));printf(&sizeof(dog)=%d\n\n&, sizeof(too.dog));printf(&sizeof(struct data)=%d\n&,sizeof(struct data)); printf(&sizeof(max)=%d\n&, sizeof(max)); printf(&sizeof(struct data)+ sizeof(max)=%d\n&, sizeof(struct data)+ sizeof(max));return 0; }运行结果是: sizeof(cat)=4 sizeof(cow)=20 sizeof(dog)=8sizeof(struct data)=32 sizeof(max)=20 sizeof(struct data)+ sizeof(max)=52 请按任意键继续. . . 内存对齐问题是一个比较难以理解的内存问题,因为摸不着、难以猜透。1.3 1.再论联合体与结构体 如程序清单 7. 6 所示,程序输出什么?程序清单 7. 6 再论联合体与结构体 union { struct { unsigned char c1:3; unsigned char c2:3; unsigned char c3:2; }s; }u; int main(int argc, char *argv[]) { u.c = 100; printf(&u.s.c1 = %d\n&, u.s.c1); printf(&u.s.c2 = %d\n&, u.s.c2); printf(&u.s.c3 = %d\n&, u.s.c3); return 0; } 这个程序考查对结构体和联合体的理解。 首先我们应该知道, 和 u.s 是在同一个地址空间中, u.c 那么 u.s 中存储的数据即为 100。 100 转化为二进制为: 。 由于是小端模式存储方式, 那么 u.s.c1 取最低三位 100, 即为十进制的 4;u.s.c2 取中间三位 100,即为十进制的 4;而 u.s.c3 取最高两位 01,即为十 进制的 1。所以输出即为:4,4,1。 第八节1.1 1. malloc内存分配与内存释放某 32 位系统下, C++程序,请计算 sizeof 的值 。char str*+ = “http://www.ibegroup.com/” ; char *p = int n = 10; 请计算 sizeof (str ) = ?(1) sizeof ( p ) = ?(2) sizeof ( n ) = ?(3) void Foo ( char str[100]){ } 请计算 sizeof( str ) = ?(4) void *p = malloc( 100 ); 请计算 sizeof ( p ) = ?(5) 答案是: (1).25 (2).4 (3).4 (4).4 (5).4不管是 int *还是 char *指针,指针长度都是 4.有了这点 sizeof(p) = 4 应该就没有任何问 题了。sizeof(n) = 4 , 因为整型长度为 4。剩下 sizeof(str)了,我们把 char str[100]变下形你可 能就知道了,其实 char str[100]和*(str+100)是等效的,也就是说传进去的是指针,而不是数 组,那么 sizeof(str) = 4 就应该可以理解了。 2. 如程序清单 8. 1 所示,请问运行 Test 函数会有什么样的结果? malloc()的应用 1程序清单 8. 1void GetMemory(char *p) { p = (char *)malloc(100); } void Test(void) { char *str = NULL; GetMemory(str); strcpy(str, &hello world&); printf(str); } 我敢说很多人看到这里会冒出来的答案是: hello world 。实际上答案是:NULL 。是 不是傻眼了。程序意图很简单,想通过 GetMenory 这个函数改变 str 的值。事实上, GetMemory( char *p )函数的形参为字符串指针, 在函数内部修改形参并不能真正的改变传入 实参的值,执行完 char *str = NULL; GetMemory( str );这 2 条程序后的 str 仍然为 NULL 。 3.如程序清单 8. 2 所示,请问运行 Test 函数会有什么样的结果? malloc()的应用 2程序清单 8. 2char *GetMemory(void) { char p[] = &hello world&; }void Test(void) { char *str = NULL; str = GetMemory(); printf(str); } 这个是 hello world 了吧 !这个还真不是输出 hello world 。有同学就要问了,str = GetMemory(),而 Getmemory()函数返回的是 p , 而 p[] = &hello world & ,怎么可能不是 hello world ! 实际上,p[]数组为函数内的局部自动变量,在函数返回后,内存已经被释放。所以 输出什么我也不知道,很可能是乱码。所以要理解变量的生存周期,否则就死翘翘了。 4. 如程序清单 8. 3 所示,请问运行 Test 函数会有什么样的结果? malloc()的应用 3程序清单 8. 3void GetMemory(char **p, int num) { *p = (char *)malloc(num); } void Test(void) { char *str = NULL; GetMemory(&str, 100); strcpy(str, &hello&); printf(str); } 有些人到这里不敢吭声了,这个会是输出什么?答案是:hello。这个题目我不分析,结 合上面 2 题请读者自己分析下。 5. 如程序清单 8. 4 所示,请问这个会是输出什么? malloc()的应用 4程序清单 8. 4#include &stdio.h& char *str() { char *p = &abcdef&; } int main(int argc, char *argv[]) { printf(&%s&, str()); return 0; } 乍眼一看, 在哪里见过?是的, 确实似曾相识。 有记忆了吧, 会不会有人立马说出答案: 输出乱码? char *GetMemory(void) { char p[] = &hello world&; } void Test(void) { char *str = NULL; str = GetMemory(); printf(str); } 上面这个题目的答案确实是:乱码! 但是 char *p = &abcdef&;和 char p[] = “abcdef”是有区别的。char *p = &abcdef&这个程序 的正确答案是输出:abcdef。 为什么?是的,有人会说,你不是说数组是可以退化成指针吗?但是,你要知道,数组 是存储在栈中,p[] = “abcdef”实在执行这条语句时,abcdef 才会赋给 p[],并且栈在执行 完成之后是会自动销毁;但是指针是保存在堆中,当开辟了*p 这个地址的时候,abcdef 就 已经存储在 p 中,然而堆只有在程序结束之后才会销毁,所以是可以输出 abcdef 这个字符 串。 1.2 malloc(0) 1. 把这个独立开来是因为很少这样使用,但是又会使用。如程序清单 8. 5 所 示,程序会输出什么? 程序清单 8. 5 malloc(0)int main(int argc, char *argv[]) { char *ptr = NULL; if ((ptr = (char *)malloc(0)) == NULL) { printf(&Null pointer\n&); printf(&ptr = %#x\n&, ptr); } else { printf(&Valid pointer\n&); printf(&ptr = %#x\n&, ptr); } return 0; } 我想很多人的第一个感知是输出:Null pointer! 但是很遗憾,是输出 Valid pointer!虽然 ptr 所开辟的内存空间为 0,但是 ptr 是不会等 于 NULL 的。第九节1.1 strcpy笔试中的几个常考题1. 已知 strcpy 函数的原型是 char *strcpy(char *strDest, const char *strSrc); 其中 strDest 是目的字符串,strSrc 是源字符串。不调用 C++/C 的字符串库函数,请编写函数 strcpy。 char *strcpy(char *strDest, const char *strSrc) { assert((strDest!=NULL) && (strSrc !=NULL)); char *address = strD while( (*strDest++ = * strSrc++) != ‘\0’ ) NULL ; } 这个题目创维和华为都曾用来做为考题。 在程序开头我们肯定要断言 strDest 和 strSrc 不等于 NULL,assert()的作用是:断言是一 个包含布尔表达式的语句,在执行这个语句时假定该表达式为 true。如果表达式计算为 false,那么系统会报告一个 Assertionerror。 我们注意到返回值是 char *类型!这里是为了实现链式表达式。将这个题目再引申下, 已知 strncpy 的函数原型是 char *strncpy( char *to, const char *from, size_t count ); 其中 to 是目的字符串,from 是源字符串。不调用 C++/C 的字符串库函数,请 编写函数 strncpy。 提示:将字符串 from 中至多 count 个字符复制到字符串 to 中。如果字符串 from 的长 度小于 count,其余部分用'\0'填补。返回处理完成的字符串。 char *strncpy(char *to, const char *from , size_t count ) { assert((to!=NULL) && (from !=NULL)); char *address = while( count != 0 ) { *address++ = *from++ ; if ( *from == '\0') { *address++ = '\0' ; } count-- ; } }1.2 CPU 的使用率 1. 写一个程序,让你的电脑 CPU 使用率一直运行在 50%。 #include &iostream& #include &stdlib.h& /* 让 CPU 的使用率在 50% */ int main(int argc, char *argv[]) { for( ; ; ) {for( int i = 0 ; i &
; i++ ) ;_sleep(10) ;} return 0; } 这里的
是根据我自己电脑算出来的,因为我的电脑主频是 2.0GHz。留一个 问题给读者,怎样让自己的电脑 CPU 以正弦曲线运行? 注意:对于新代处理器由于优化,可能做不到。1.3 二进制数据中 1 的个数 1. 写一个程序,随意输入 x,输出 x 的二进制数据中 1 的个数。 这个程序的算法很多,可以一位一位右移进行测试,也有其他办法。右移法我就不再累 赘,这个方法简单,但是时间复杂度会比较大。看看下面这个方法: int number( unsigned int x ) { unsigned int countx = 0;while (x) {countx ++ ; x = x&(x-1) ;} } 如果 x 大于 0,那么 x 一定有一位为 1,所以进入 while 之后 countx 先加 1。如果 x=100, 那么经过 x=x&(x-1),x 为 0,countx 为 1,此时结束程序。1.4 二进制高位到低位实现逆变 1. 编写一个函数, 实现将一个 32 位 int 型数据的二进制高位到低位实现逆变, 例如:
变成 。 这个题目的解决方法很多,代表性的有两种。 int func(unsigned int uiData , int length) { unsigned int uiValue = 0 ; int i =0;for ( i = 0 ; i & i++ ) { uiValue = (uiValue && 1) + (uiData & 0x01) ; uiData } return uiV } 这个方法比较常见,通过移位的方法将高位到低位实现逆序。但是这个方法存在唯一的 不足之处是效率低,要进行 32 次移位和运算。 int func (unsigned int uiData) { unsigned int uiValue = 0 ; = uiData && 1 ;/* 分而治之的思想 */ /* 高 16 位和低 16 互换 */ uiValue = ((uiData && 16)&0x0000ffff) | ((uiData && 16)&0xffff0000);/*高低 16 位中的高低 8 位互换*/ uiValue = ((uiValue && 8)&0x00ff00ff) | ((uiValue && 8)&0xff00ff00);/*8 位中的高低 4 位互换*/ uiValue = ((uiValue && 4)&0x0f0f0f0f) | ((uiValue && 4)&0xf0f0f0f0);/*4 位中的高低 2 位互换*/ uiValue = ((uiValue && 2)&0x) | ((uiValue && 2)&0xcccccccc);/*2 位中的高低位互换*/ uiValue = ((uiValue && 1)&0x) | ((uiValue && 1)&0xaaaaaaaa);return uiV } 这个程序只需要位操作 5 次,就能实现高位到低位的逆序。我们逐句程序分析一下。假 设 uiData = 01 01 。执行完成下面这句程序, /* 高 16 位和低 16 互换 */ uiValue = ((uiData && 16)&0x0000ffff) | ((uiData && 16)&0xffff0000); 得到 01 01 ,也就是高 16 位和低 16 位互换。执行完成: /*高低 16 位中的高低 8 位互换*/ uiValue = ((uiValue && 8)&0x00ff00ff) | ((uiValue && 8)&0xff00ff00); 得到 00 00 ,也就是高低 16 位中高 8 位和低 8 位互 换。执行完成: /*8 位中的高低 4 位互换*/ uiValue = ((uiValue && 4)&0x0f0f0f0f) | ((uiValue && 4)&0xf0f0f0f0); 得到 01 01 , 也就是从高位起, 8 位段的高 4 位和 每 低 4 位完成互换。执行完成: /*4 位中的高低 2 位互换*/ uiValue = ((uiValue && 2)&0x) | ((uiValue && 2)&0xcccccccc); 得到 01 01 , 也就是从高位起, 4 位段的高 2 位和 每 低 2 位完成互换。 执行完成: /*2 位中的高低位互换*/ uiValue = ((uiValue && 1)&0x) | ((uiValue && 1)&0xaaaaaaaa); 得到 10 10 。 也就是从高位起, 2 位段的高 1 位和 每 低 1 位完成互换。和原始数据 01 01
进行对比,逆序。1.5 大小端测试 1. 编写一个函数,测试 MCU 是大端模式存储还是小端模式存储。 /**************************************************************** ** 函数名称:LBEndian ** 函数功能:大小端测试函数 ** 入口参数:None ** 出口参数:1 or 0 ****************************************************************/ int LBEndian (void) { unsigned int uiNumber = 0x ; = (void *)0 ;unsigned char *ucptr/* 将最低位 1 一个字节赋给 ucptr */ ucptr = (unsigned char *)(&uiNumber) ;/* 如果是小段模式,则返回 1*/ if ( 0x78 == (*ucptr) ) { return 1 ; } /* 如果是大端模式,则返回 0 */ else { return 0 ; } } ucptr = (void *)0 ,这样做是为了防止野指针的危患。通过 ucptr = (unsigned char *)(&uiNumber) (好好理解这句程序);截取低地址的存储字节数据,如果低地址存储的是低字 节,那么就是小端模式,如果低字节存储的是高字节,那么就是大端模式。1.6 二分查找 1. 写一个函数实现二分查找 int BinarySeach(int *iArray, int key, int n) { int iLow = 0 ; int iHigh = n - 1; int iMwhile (iLow &= iHigh) { iMid = (iHigh + iLow)/2; if (iArray[iMid] & key) { iHigh = iMid - 1 ; } else if (iArray[iMid] & key) { iLow = iMid + 1 ; } else { return iM } } } 数据结构中的各种查找算法在笔试中是无处不在,在工程应用中也是“无孔不入” 。所 以作为一个软件工程师,必须牢牢掌握各种查找算法。1.7 int (*p[10])(int) 1. int (*p[10])(int) 表示的是什么? 函数指针数组,int(*p)(int),我们知道这是一个函数指针,一个指向 int Fun(int)函数的 指针,那么 int (*p[10])(int)即为函数指针数组。1.8 对绝对地址赋值的问题 涉及到内存的问题, 都让很多人望而却步, 因为内存确实是地雷阵, 稍不小心就会引爆。 1. 要对绝对地址 0x10 0000 赋值,我们该怎么做? *(unsigned int *)0x10 0000 = 1234 ; 通过这个程序我们把常量 1234 存储在地址为 0x10 0000。2.如果想让程序跳转到绝对地址为 0x10 0000 去执行,应该怎么做?*( (void (*)( ))0x100000 ) ( ); 首先要将 0x10 0000 转换成函数指针: (void (*)( ))0x100000 然后再调用他: *( (void (*)( ))0x100000 ) () ; 因为内存是摸不着,猜不透的,所以很像地雷阵,随时都可能挂掉。第十节 序数据结构之冒泡排序、选择排我相信很多人曾经写冒泡排序和选择排序都是一个算法一个代码,并且还一个一个 写得不亦乐乎。zzq 宁静致远今天就告诉你如何写出一手漂亮的 C 语言代码,当你看完 今天的帖子,你就会恍然顿悟曾经自己写的代码如此不堪。 1. 冒泡排序 1.1 底层冒泡排序的头文件 为了增强代码的可移植性,健壮性。我们将冒泡排序的算法封装在库中,我们只需要调 用库函数即可。冒泡排序头文件程序如程序清单 1. 1 所示。 程序清单 1. 1 冒泡排序头文件 /* * 声明比较函数,升序还是降序 */ typedef int (*COMPAREFUN)(const void *pvData1, const void *pvData2); /************************************************************************** ***** **函数名称: bubbleSort **函数功能: 冒泡排序 **入口参数: *pvData: 需要进行排序的数组 stAmount: 数组中包含的元素个数 stSize: 元素内存大小 CompareFun: 需要排序的数据类型、升序还是降序 **出口参数: *************************************************************************** ****/ extern void bubbleSort (void *pvData, size_t stAmount, size_t stSize , COMPAREFUN CompareFun); 为了各种数据的兼容性,所有不确定情况的数据类型都使用 void *。 1.2 底层数据交换函数实现 通过函数指针类型数据,向 swap 函数传入需要交换的两个数据的地址和数组元素内存 大小,实现数据的交换。swap 函数代码如程序清单 1. 2 所示。 程序清单 1. 2 swap 函数 /************************************************************************** ***** **函数名称: __swap **函数功能: 数据交换 **入口参数: *pvData1: 需要交换的数据一 *pvData2: 需要交换的数据二 stDataSize:元素内存大小 **出口参数: *************************************************************************** ****/ static void __swap (void *pvData1, void *pvData2, size_t stDataSize) { unsigned int *p1=(unsigned int)pvData1; unsigned int *p2=(unsigned int)pvData2; unsigned int uiT while ( stDataSize &= sizeof(unsigned int) ) //一次交换 sizeof(unsigned int)个字节 { (*p1) ^=(*p2); (*p2) ^=(*p1); (*p1++)^=(*p2++); stDataSize -= sizeof(unsigned int); } if (stDataSize&0) { /* * void *memmove( void *to, const void *from, size_t count ); * 函数从 from 中复制 count 个字符到 to 中,并返回 to 指针。 */ memmove( &uiTemp,p1,stDataSize); memmove( p1,p2,stDataSize); memmove( p2,&uiTemp,stDataSize); } } 这里传进去的是三个参数分别是:pvData1,为需要交换的两个数据中的第一个数据的 地址;pvData2,为需要交换的两个数据中的第二个数据的地址;stDataSize:数组中元素内 存的大小。 传进去之后, 先将两个数据的地址强制转化为(int*)型地址。 数据的交换分成两个部分: 如果元素内存大小大于一个 sizeof(unsigned int)个字节大小, 则一次性交换 4 位; stDataSize 当 大于 0 且小于一个 sizeof(unsigned int)个字节大小时,再通过 memmove 交换剩下的部分。 1.3 冒泡排序算法实现 冒泡排序的基本思想是通过相邻元素之间的比较和交换使得排序码较小的元素上移或 下移。冒泡排序代码如程序清单 1. 3 所示。 程序清单 1. 3 冒泡排序 /************************************************************************** ***** **函数名称: bubbleSort **函数功能: 冒泡排序 **入口参数: *pvData: 需要进行排序的数组 stAmount: 数组中包含的元素个数 stSize: 元素内存大小 CompareFun: 需要排序的数据类型、升序还是降序 **出口参数: *************************************************************************** ****/ void bubbleSort (void *pvData, size_t stAmount, size_t stSize , COMPAREFUN CompareFun) { int i, int iNoSwapFlg = 0; void *pvThis = NULL; void *pvNext = NULL; /* * 冒泡排序 */ i = stAmount - 1; do { iNoSwapFlg = 0; pvThis = pvD pvNext = (char *)pvData + stS j = do { if (CompareFun(pvThis, pvNext) & 0) { __swap(pvThis, pvNext, stSize); iNoSwapFlg = 1; } pvThis = pvN pvNext = (char *)pvNext + stS } while (--j != 0); if (iNoSwapFlg == 0) { } } while ( --i != 0); } bubbleSort 函数传入的有四个参数:pvData:需要进行排序的数组的首元素地址,但是 这个地址也就是需要进行排序的数组的地址。 这个区别就好像是广东的省政府在广州, 而广 东省首号城市广州市的市政府也在广州, 虽然地址相同, 但是意义不同。 为了证明这个观点, 我定义了两个数组进行测试。 static int iArray[] = {39, 33, 18, 64, 73, 30, 49, 51, 81}; static char *strArray[] ={&forARM&,&mzdzkj&,&c language&,&shenzhen&,&china&}; printf(&&iArray = %#x \n& , &iArray ) ; printf(&&iArray[0] = %#x \n& , &iArray[0] ) ; printf(&strArray = %#x \n& , strArray ) ; printf(&&strArray = %#x \n& , &strArray ) ; 编译之后运行的结果为: &iArray = 0x402000 &iArray[0] = 0x402000 strArray = 0x402024 &strArray = 0x402024 所以在这个函数中,无论传入的是数组的首元素地址,还是数组的地址,都是可以的, 因为有这么一句程序: pvNext = (char *)pvData + stS 所以无论如何,pvNext 都是下一元素的地址。 测试程序: printf(&(char*)&iArray[0] + sizeof(iArray[0]) = %#x \n& , (char*)&iArray[0] + sizeof(iArray[0]) ) ; printf(&&iArray[1] = %#x \n\n& , &iArray[1] ) ; printf(&(char*)&strArray[0] + sizeof(strArray[0]) = %#x \n& , (char*)&strArray[0] + sizeof(strArray[0]) ) ; printf(&&strArray[1] = %#x \n& , &strArray[1] ) ; 结果: (char*)&iArray[0] + sizeof(iArray[0]) = 0x402004 &iArray[1] = 0x402004 (char*)&strArray[0] + sizeof(strArray[0]) = 0x402028 &strArray[1] = 0x402028 stAmount:数组中包含的元素个数,我们通常使用:sizeof(strArray) / sizeof(strArray[0], 即为数组总长度除以元素内存大小,这个结果就是数组元素的个数。 stSize:元素内存大小,sizeof(strArray[0],因为数组内每一个元素的类型相同,所以每 个元素的内存大小也就相同。 CompareFun:需要排序的数据类型、升序还是降序。这个函数的原型是: typedef int (*COMPAREFUN)(const void *pvData1, const void *pvData2); 如果是整型数组需要升序排列,则函数为如程序清单 1. 4 所示: 程序清单 1. 4 整型数组升序 /************************************************************************** ***** **函数名称: int_Rise_cmp **函数功能: 对 int 进行升序排列 **入口参数: *x: *y: **出口参数: ( *(int *)x - *(int *)y ) 确定升序 *************************************************************************** ****/ int int_Rise_cmp(void *x , void *y) { return ( *(int *)x - *(int *)y ); } 我们就综合上述对其进行一个整体的分析。假设需排序的数组为:static int iArray[] = {39, 33, 18, 64, 73, 30, 49, 51, 81};pvData 是需排序数组的首元素地址,由: pvThis = pvD pvNext = (char *)pvData + stS 那么 pvThis 即为数组首元素的地址,也就是&iArray[0],pvNext 为下一个元素的地址,也 就是&iArray[1]。 接着通过 CompareFun(pvThis, pvNext)比较两个元素的大小, 进入 CompareFun, 也就是 int_Rise_cmp 函数,x 即为 pvThis,y 即为 pvNext。这样 x 即为数组首元素的地址, 这里还是 void*, 我们通过强制转化, x 指向整型, 将 即为(int*)x, 再去地址, 也就是( *(int *)x, 数组首元素, 以此类推。 y 如果( *(int *)x - *(int *)y ) &0, 也就是 CompareFun(pvThis, pvNext)&0, 则交换这两个数据,从而达到从小到大排列的目的。交换完成之后, pvThis = pvN pvNext = (char *)pvNext + stS 这样以此类推。 static int iArray[] = {39, 33, 18, 64, 73, 30, 49, 51, 81}; static char *strArray[] ={&forARM&,&mzdzkj&,&c language&,&shenzhen&,&china&}; 第二个数组值得一提,这是一个指针数组,即为数组中存储的是指针变量。不相信的话 可以测试一下。 printf(&strArray[0] = %#x \n\n& , strArray[0] ) ; 结果是: strArray[0] = 0x403000 很显然是指针。上述两个数组经过排序之后的测试结果如程序清单 1. 5 所示。 程序清单 1. 5 测试结果 *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 整型数组数据排序之前: 39 33 18 64 73 30 49 51 81 字符串数组排序之前: 'forARM' 'mzdzkj' 'c language' 'shenzhen' 'china' 整型数组数据升序之后: 18 30 33 39 49 51 64 73 81 整型数组数据降序之后: 81 73 64 51 49 39 33 30 18 字符串数组数据升序之后: 'c language' 'china' 'forARM' 'mzdzkj' 'shenzhen' 字符串数组数据降序之后: 'shenzhen' 'mzdzkj' 'forARM' 'china' 'c language' 2.选择排序 2.1 选择排序算法 一个好的迭代器,只需要修改排序算法,其他的程序都无需修改。其实这里只需要把冒 泡排序算法修改为选择排序算法即可。 选择排序算法程序如程序清单 2. 1 所示。 程序清单 2. 1 选择排序函数 /************************************************************************** ***** **函数名称: selectSort **函数功能: 选择排序 **入口参数: *pvData: 需要进行排序的数组 stAmount: 数组中包含的元素个数 stSize: 元素内存大小 CompareFun: 需要排序的数据类型、升序还是降序 **出口参数: *************************************************************************** ****/ void selectSort (void *pvData , size_t stAmount, size_t stSize , COMPAREFUN CompareFun) { int i , j , void *pvThis = NULL; void *pvThat = NULL; /* * 冒泡排序 */ #if 0 printf(&pvData = %#x\n& ,pvData ) ; printf(&pvThis = %#x\n& ,pvBegin ) ; printf(&pvThat = %#x\n& ,pvEnd ) ; #endif for ( i = 0 ; i & stA i++ ) { k=i; for ( j = i + 1 ; j & stA j++) { pvThis = (char *)pvData + j*stS pvThat = (char *)pvData + k*stS if (CompareFun(pvThis , pvThat ) & 0) { k=j; } if( k != i ) { pvThis = (char *)pvData + i*stS pvThat = (char *)pvData + k*stS __swap(pvThis , pvThat , stSize); } } } } 其实这个选择排序函数和冒泡排序函数只是改动了内部程序,其他地方都没有修改。道 理是一样,我就不加说明。 触类旁通的思想真的很重要,当你庖丁解牛对待一个冒泡排序的时候,你会发现其他排 序方法也就自然而然会了。 我们看看测试结果: *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 先测试一些数据,便于我们理解 第一组数据: sizeof(iArray) = 36 sizeof(iArray[0]) = 4 &iArray = 0x402000 &iArray[0] = 0x402000 (char*)&iArray[0] + sizeof(iArray[0]) = 0x402004 &iArray[1] = 0x402004 &iArray[8] = 0x402020 第二组数据: sizeof(strArray) = 20 sizeof(strArray[0]) = 4 strArray = 0x402024 &strArray = 0x402024 &strArray[0] = 0x402024 (char*)&strArray[0] + sizeof(strArray[0]) = 0x402028 &strArray[1] = 0x402028 strArray[0] = 0x403000 *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 整型数组数据排序之前: 39 33 18 64 73 30 49 51 81 字符串数组排序之前: 'forARM' 'mzdzkj' 'c language' 'shenzhen' 'china' 整型数组数据升序之后: 18 30 33 39 49 51 64 73 81 整型数组数据降序之后: 81 73 64 51 49 39 33 30 18 字符串数组数据升序之后: 'c language' 'china' 'forARM' 'mzdzkj' 'shenzhen' 字符串数组数据降序之后: 'shenzhen' 'mzdzkj' 'forARM' 'china' 'c language' 请按任意键继续. . . 测试通过!第十一节机试题之数据编码某部队为了防止消息泄密从而对原始数据进行编码,编码规则如下。 1) 所有信息都为 ASCII 编码;2) 在收到原始密文后将字符进行二进制逆转,如字符'A'(0x41,B)将 数据逆转后为(0x82,B); 3) 将逆转后的数据按照 16 进制打印输出 (原始数据允许空格)如字符串&ABCD , EFGH&加密后的输出结果为:&A262E212&。 为了加快编码解码速度现在需要你编写一个程序实现该密文的编码。 这个题目说到底就是将一个字符转化成二进制,再将这个二进制的高低位逆转,之后输 出逆变后对应数据的 ASCII。 二进制高低位逆转在 12.4 有详细讲解,为了算法不重复,这里采用逐位逆转方法进行 解答。 // text.cpp : 定义控制台应用程序的入口点。#include &stdafx.h& #include &string.h&/**************************************************************** ** 函数名称: Printb** 函数功能: 输入一个数据,将其二进制反位输出 ** 入口参数: ** 出口参数: ** 返回值 : uiValue:待转换的值 None uiSum: 转换之后的值****************************************************************/ unsigned int Printb( unsigned int uiValue) {unsigned int uiSum = 0 ;/* 将数据的二进制逆位 */ for ( int i = 0 ; i & 8 ; i++ ) { uiSum = ( uiSum && 1 ) + ( uiValue & 0x01 ) ; uiValue = ( uiValue && 1 ) ; }return uiS }/**************************************************************** ** 函数名称: main** 函数功能: 主函数 ** 入口参数: ** 出口参数: argc,* argv[] none** 返回值 : 0 ****************************************************************/ int main(int argc, char* argv[]) {char s[100] ; unsigned int iArray[100] ;scanf( &%s& , s ) ; /* 将字符串转化为整型数据 */ for ( int i = 0 ; i & strlen(s) ; i++ ) { iArray = }/* 以十六进制输出字符串数据 */ for ( int j = 0 ; j & strlen(s) ; j++ ) { printf( &%x& , iArray[j] ) ; }printf( &\n& ) ;/* 以十六进制输出译码后的字符串数据 */ for ( int k = 0 ; k & strlen(s) ; k++ ) { printf( &%x& , Printb( iArray[k] ) ); }printf(&\n&) ; return 0; }编译结果: ABCDEFGH 2a262e212 请按任意键继续. . .第十二节 机试题目之十进制 1~N 的所 有整数中出现“1”的个数给定一个十进制数 N, 写下从 1 开始到 N 的所有整数, 然后数一下其中出现的所有 “1” 的个数,比如: 1) 2) 5。 问题是:写一个函数 f(N),返回 1~N 之间出现“1”的个数,比如:f(12) = 5。 N = 2 ,写下 1、2,这样只出现 1 个“1” ; N= 12 ,我们会写下 1、2、3、4、5、6、7、8、9、10、11、12,这样 1 的个数为这个题目带有几分找规律性质。本题解法可能较多,这里提供两种。 方法一: // text.cpp : 定义控制台应用程序的入口点。#include &stdafx.h& /**************************************************************** ** Function name: ** Descriptions: oneNumber 计算 1 的个数** input parameters: uliNumber:输入的数据,即为要计算 1 的个数的数据 ** output parameters: void ** Returned value: uliCount:1 的个数****************************************************************/ unsigned long int oneNumber ( unsigned long int uliNumber ) {/* 将 uliNumber 传进来的值赋给 uliTally */ unsigned long int uliTally /* 记录 1 的个数 */ unsigned long int uliCount /* 提取位的权值 */ unsigned long int uliFlag /* 提取位 */ unsigned int uiFlag =0; =1; =0; = uliN/* 提取位的幂次方 */ unsigned int uiLog =0; /* *对数据逐位取提取位 */ while ( ( uliTally / uliFlag ) != 0 ) {/* 从左开始计算,依次取出 uiFlag*10^n */ uiFlag = ( uliTally / uliFlag ) % 10 ;/* 如果是 1 * 10^n ,则按 1*10^n 公式进行计算 */ if ( 1 == uiFlag ) { uliCount += uiLog * ((unsigned long int)( uliFlag / 10)) + 1 ; /* 加上 10^n 后面数据 */ uliCount += ( uliNumber % uliFlag ) ; } /* *如果是 uiFlag * 10^n ,则按 uiFlag*10^n 公式进行计算 */ else { /*( uliFlag ) * ( 1&&uiFlag )是为了 uliFlag 是 0,则是加 0 */ uliCount += ( uliFlag ) * ( 1&&uiFlag ) + uiFlag * uiLog * ( (unsigned long int)( uliFlag / 10) ) ; }/* 依次向左取 */ uliFlag *= 10 ; /* uiLog = log10(uliFlag) */ uiLog += 1 ;} /* 返回 1 的个数 */ return uliC}/**************************************************************** ** Function name: ** Descriptions: ** input parameters: main 输入输出 argc , argv[]** output parameters: void ** Returned value: 0****************************************************************/ int main(int argc, char* argv[]) {unsigned long int iNumber = 0 ; unsigned long int uliCountNumber = 0 ; printf(&请输入 iNumber: &) ; scanf(&%ld& , &iNumber) ;uliCountNumber =oneNumber(iNumber) ;printf( &1~%d 中 1 的个数为: %d\n& , iNumber , uliCountNumber ) ;return 0; } 函数头文件这里使用的是英语,但是格式是不变的。 编译结果: 请输入 iNumber: 100 1~100 中 1 的个数为: 21 请按任意键继续. . .方法二: // text.cpp : 定义控制台应用程序的入口点。 #include &stdafx.h&/**************************************************************** ** Function name: ** Descriptions: ** input parameters: oneNumber 计算 1 的个数 uliNumber:输入的数据,即为要计算 1 的个数的数据** output parameters: void ** Returned value: uliCount:1 的个数****************************************************************/ unsigned long int oneNumber ( unsigned long int uliNumber ) { /* ** 记录 1 的个数 */ unsigned long int uliCount /* ** 提取位左边的数 */ unsigned long int uliLeft /* ** 提取位右边的数 */ =0; =0; unsigned long int uliRight /* ** 提取位,此位对 1 进行计数 */ unsigned long int uliFlag /* ** 提取位的权值 */ unsigned int uiFlag=0;=0;=1;/* ** 对数据逐位取提取位 */ while ( (uliNumber / uiFlag)!=0 ) {/* ** 提取位右边的数 */ uliRight = uliNumber % uiFlag /* ** 提取位 ; */ uliFlag = ( uliNumber / uiFlag ) % 10 ; /* ** 提取位左边的数 */ uliLeft = ( uliNumber / uiFlag ) / 10 ; /* ** 判断提取位,分成 0、1 和大于等于 2 这三种情况 */ switch ( uliFlag ) {/* ** 如果提取位为 0,那么 1 的个数等于提取位左边数乘以取提取位的数据 */ case 0 : { uliCount += uliLeft * uiF } /* ** 如果提取位为 1,那么 1 的个数等于提取位左边数乘以取提取位的数据 ** 再加上(0 到提取位右边数)+1 */ case 1 : { uliCount += uliLeft * uiFlag + uliRight + 1 ; } /* ** 如果提取位大于 1,那么 1 的个数等于(提取位左边数+1)乘以 ** 取提取位的数据 */ default : { uliCount += ( uliLeft + 1 ) * uiF }} /* ** 提取位向左移动一位 */ uiFlag *= 10 ; } /* ** 返回 1 的个数 */ return uliC}/**************************************************************** ** Function name: ** Descriptions: ** input parameters: main 输入输出 argc , argv[]** output parameters: void ** Returned value: 0****************************************************************/ int main(int argc, char* argv[]) { unsigned long int iNumber = 0 ; unsigned long int uliCountNumber = 0 ; printf(&请输入 iNumber: &) ; scanf(&%ld& , &iNumber) ;uliCountNumber =oneNumber(iNumber) ;printf( &1~%d 中 1 的个数为: %d\n& , iNumber , uliCountNumber ) ; return 0; }第十三节 机试题之 找出链表中间元素遍历单链表一次,单链表最大的特点就是“不走回头路” ,不能实现随机存取。如果我们想要找一个数组 a 的中间元素,直接 a[len/2]就可以了,但是链表不行,因为只有 a[len/2 - 1] 知道 a[len/2], 其节点不知道。因此,如果按照数组的做法依样画葫芦,要找到链表的中点,我们需要做两 步(1)知道链表有多长(2)从头结点开始顺序遍历到链表长度的一半的位置。这就需要 1.5n (n 为链表的长度) 的时间复杂度了。 有没有更好的办法呢?答案是有的。 想法很简单: 两个人赛跑,如果 A 的速度是 B 的两倍的话,当 A 到终点的时候,B 应该刚到中点。这只需 要遍历一遍链表就行了,还不用计算链表的长度。 下面这个程序算法就是只遍历单链表一次,即能找出链表中间元素。 typedef struct _list_node { int iD_list_node * }ListNListNode *FindList(ListNode *head) { ListNode *p1, *p2;if ( (NULL == head) || (NULL == head-&next) ) { }p1 = p2 =while (1) { if ( (NULL != p2-&next) && (NULL != p2-&next-&next) ) { p2 = p2-&next-& p1 = p1-& } else { } } return p1; }第十四节机试题之全排序 写 一 个 程 序 , 对 任 意 一 串 字 符 串 进 行 全 排 序 。 如 123 的 全 排 序 为 : 123,132,213,231,312,321. 这个题目使用数学很简单,因为高中的排列组合一个式子就把这个题目给 KO 了,C 语 言其实也很简单,无非是列举出所有排列顺序罢了。 // text.cpp : 定义控制台应用程序的入口点。#include &stdafx.h& //读者在这里思考两个问题: //1.这个函数的作用是什么? //2.inline 是做什么用的? inline void Swap(

我要回帖

更多关于 哪个西方国家我没去过 的文章

 

随机推荐