朱兆祺教你如何攻破C语言学习、笔试与机试的难点(连载)

qinkaiabc   2013-11-27 19:33 楼主
第一节  C语言编程中的几个基本概念

1.1      include< >与#include" "
1.   #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;
}

回复评论 (18)

第二节  数据存储与变量

2.1      变量的声明与定义
1.         如程序清单2. 1所示会不会报错?为什么?如果不会报错,又是输出什么结果?
程序清单2. 1  变量的声明与定义
#include

static int  a   ;
static int  b[] ;

int main( int argc , char *argv[] )
{
    printf( "%d  %d \n" , a , b[0] ) ;
    return 0 ;
}

static int   a = 8 ;
static int   b[4]  ;
这个程序是不会报错的,并且连警告都不会出现。输出的结果是:8  0
static int a ,这句程序是声明全局变量a;static int b[],这句程序是声明全局数组变量b,并且是不完全声明,也就是可以省略数组下标。static int a = 8,这里才是定义全局变量a,static int b[4],这里是定义全局变量b。
2.2      局部变量与全局变量的较量
1.         请问如程序清单2. 2所示输出什么?
程序清单2. 2  局部变量与全局变量
#include

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是输出:0x4040 0000。有人会问了,难道j是随机输出?瞎说,j输出0x4040 0000是有依据,是一个定值!
由于i是float数据类型,而j是int数据类型。理论上说,j是取了i的地址然后再去地址,应该得到的就是i的值:3。但是问题的关键就是float数据类型的存储方式和int数据类型不一样,float是占用4个字节(32位),但是float存储是使用科学计数法存储,最高位是存储数符(负数的数符是0,正数的数符是1);接下来8位是存储阶码;剩下的23位是存储尾数。上面i=3.000000,那么3.000000(10进制) = 11(2进制) = (二进制)。数据在电脑中存储都是二进制,这个应该都没有疑问。那么这里的数符为:0 ,阶码为:E – 127 = 1 ,那么阶码为:E = 128 即为:1000 0000 (2进制) ,尾数为:100 0000 0000 0000 0000 0000 。那么存储形式就是:0100 0000  0100 0000 0000 0000 0000 0000。这个数据转换成16进制就是0x4040 0000。
图2. 1  数据存储方式
085206tqratawq7qgttyew.jpg.thumb.jpg
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取字符型数据

085614glg0zch3zcz1cc6u.jpg.thumb.jpg
由于( (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取整型数据

085617wpwlpm7lp4y465ft.jpg.thumb.jpg
以此类推,*( (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)。
点赞  2013-11-27 19:37
第三节  数学算法解决C语言问题

3.1      N!结果中0的个数
1.        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   

int fun(int iValue)   
{   
    int iSum = 0;   
    while(iValue / 5 != 0)   
    {   
        iSum     += (iValue / 5 );   
        iValue  /= 5;   
    }   
    return iSum;   
}   

int main( int argc , char *argv[] )   
{   
int iNumber, iZoreNumber;  

    scanf( "%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
#include
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位
请按任意键继续. . .
    网友可以自行验证。
点赞  2013-11-27 19:38
第四节  关键字、运算符与语句
1.1      static
1.         如程序清单4. 1所示,请问输出i、j的结果?
程序清单4. 1  static
#include

static int  j ;

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 = 1

i = 2   j = 1

i = 3   j = 1

i = 4   j = 1

i = 5   j = 1

i = 6   j = 1

i = 7   j = 1

i = 8   j = 1

i = 9   j = 1

i = 10   j = 1

请按任意键继续. . .

很多人傻了,为什么呢?是啊,为什么呢?!
由于被static修饰的变量总存在内存静态区,所以运行这个函数结束,这个静态变量的值也不会被销毁,函数下次使用的时候仍然能使用这个值。
有人就问啊,为什么j一直是1啊。因为每次调用fun2()这个函数,j都被强行置0了。
static的作用:
(1) 函数体内 static 变量的作用范围为该函数体,不同于 auto 变量,该变量的内存只被分配一次, 因此其值在下次调用时仍维持上次的值;
(2) 在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;
(3) 在模块内的 static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明 它的模块内;
(4) 在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
(5) 在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的static 成员变量。

1.2      for循环的深究
1.         如程序清单4. 2所示,输出结果是什么?
程序清单4. 2  for循环
#include

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  Second = 0  Fourth = 0
Third = 1  Second = 1  Fourth = 1
Third = 2  Second = 2  Fourth = 2
Third = 3  Second = 3  Fourth = 3
Third = 4  Second = 4  Fourth = 4
Third = 5  Second = 5  Fourth = 5
Third = 6  Second = 6  Fourth = 6
Third = 7  Second = 7  Fourth = 7
Third = 8  Second = 8  Fourth = 8
Third = 9  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
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
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. 5  scanf()函数的输入
#include

int main(void)
{
        float fDividend,fDivisor,fResult;

        printf("Enter Dividend:");
        scanf("%f",&fDividend);

        printf("Enter Divisor:");
        scanf("%f",&fDivisor);

        fResult=fDividend/fDivisor;
        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

int main( int argc , char *argv[] )
{
    float   fDividend , fDivisor , fResult ;
    int     iRet ;
    char    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 / fDivisor ;
            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      scanf()函数的返回值
1.         如程序清单4. 6所示,请问输出会是什么?
程序清单4. 6  scanf()函数的返回值
int a , b ;
printf( "%d \n" , scanf("%d%d" , &a , &b) ) ;
输出输入这个函数的返回值?!答案是:2。只要你合法输入,不管你输入什么,输出都是2。那么我们就要深入解析scanf这个函数。scanf()的返回值是成功赋值的变量数量。

1.7      const作用下的变量
1.         阅读如程序清单4. 7所示,想想会输出什么?为什么?
程序清单4. 7  const作用下的变量
const int   iNumber = 10 ;
printf(" iNumber = %d  \n" ,  iNumber) ;
int *ptr = (int *)(&iNumber) ;
*ptr  = 100 ;
printf(" iNumber = %d  \n" ,  iNumber) ;
const的作用在第四章已经详细讲了,这里就不再累赘,答案是:10,10。这里补充一个知识点:
const int *p                   指针变量p可变,而p指向的数据元素不能变
int* const p                   指针变量p不可变,而p指向的数据元素可变
const int* const p            指针变量p不可变,而p指向的数据元素亦不能变

1.8      *ptr++、*(ptr++),*++ptr、*(++ptr),++(*ptr)、(*ptr)++的纠缠不清
1.         如程序清单4. 8所示程序,输出什么?
程序清单4. 8  *ptr++
int  iArray[3] = { 1 , 11 , 22} ;
int  *ptr     = iArray ;

printf( "*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++


091402t2mmos8lz5dy3k57.jpg.thumb.jpg






如程序清单4. 9所示程序,输出会是什么?
程序清单4. 9  *++ptr
int  iArray[3] = { 1 , 11 , 22} ;
int  *ptr       = iArray ;

printf( "*++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       = iArray ;

printf( "(*ptr)++ = %d \n" , (*ptr)++ ) ;
printf( "*ptr   = %d \n" , *ptr   ) ;
这个的输出是:1,2。原因请读者分析。
点赞  2013-11-27 19:44
第五节    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+x
int 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
int func(int a)
{
    if (a==0)
    {
       return a;
    }
    printf("%d\n",func(a++/2));
    return a;
}

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+++j ;
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 – ((1234 >> 5) << 5);
1234 / 16 = 77; 1234 % 32 = 18。而十进制:1234转化成二进制:0100 1101 0010。1234 >> 4 = 0000 0100 1101,转化为十进制即为:77;1234 >> 5 = 0000 0010 0110,((1234 >> 5) << 5)即为0100 1100 0000,转化为十进制即为:1120,1234 1216 = 18。
点赞  2013-11-27 19:45
第六节  数组与指针


1.1      数组、数组元素、指针的大小
1.         如程序清单6. 1所示,程序输出什么?
程序清单6. 1  数组、数组元素、指针的大小
#include
int main(int argc, char *argv[])
{
    int *p ;
    printf( "sizeof(p)       = %d \n"  ,  sizeof(p)  ) ;
    printf( "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)       = 4
sizeof(*p)      = 4
sizeof(a)       = 400
sizeof(a[100]) = 4
sizeof(&a)      = 4
sizeof(&a[0])   = 4
请按任意键继续. . .
1.2      广东省的省政府和广州市的市政府都在广州
1.         如程序清单6. 2所示,如果ptr = 0x1000 0000 ,那么剩下三个输出是多少?
程序清单6. 2  数组首地址与数组首元素地址
int iArray[3] = { 1 , 2 , 3 } ;
int *ptr      = iArray ;

printf("ptr          = %#x\n" , ptr ) ;
printf("iArray       = %#x\n" , iArray ) ;
printf("&iArray      = %#x\n" , &iArray ) ;
printf("&iArray[0]   = %#x\n" , &iArray[0] ) ;
092631pc1grv1d105u0100.jpg.thumb.jpg
iArray是数组名,由6.1节对a的说明,可iArray知同时也是数组首元素的地址,为0x1000 0000;&iArray是数组的首地址,这是毫无疑问的。&iArray[0]是数组首元素的地址,也是0x1000 0000。也就是说数组的首地址和数组首元素的地址是相等的。因为广东省的省政府和广东省的首号城市广州市的市政府都在广州,两者的地址是相等的。如图6. 1所示。
如程序清单6. 3所示,ptr = 0x1000 0000 ,那么这三个输出会是多少?
程序清单9. 3  数组首地址加1、数组首元素地址加1
int iArray[3] = { 1 , 2 , 3 } ;
int *ptr      = iArray ;

printf("&iArray+1     = %#x\n" , &iArray+1 ) ;
printf("iArray+1      = %#x\n" , iArray+1 ) ;
printf("&iArray[0]+1 = %#x\n" , &iArray[0]+1 ) ;
答案是,第一个输出:0x1000 000C;第二个、第三个输出:0x1000 0004。
&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
int main(int argc, char *argv[])
{
    int  a[2] = { 3 , 6 } ;
    int  *p   = a ;
    int  *q   = 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

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 = 0x801005
p2+5 = 0x810014
请按任意键继续. . .
1.6      数组与指针的概念
1.         如程序清单6. 7所示,解释程序。
程序清单6. 7  数组与指针的概念
a) int 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)   一个有10个整型数的数组;
e)   一个有10个指针的数组,该指针是指向一个整型数的;
f)   一个指向有10个整型数数组的指针;
g)   一个指向函数的指针,该函数有一个整型参数并返回一个整型数;
h)   一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数。
这个题目很经典,很多公司的笔试题目都会截取上面部分出来考试。特别是e和f,哪一个是数组指针,哪一个又是指针数组;g和h哪一个是函数指针,哪一个又是指针函数。
1.7      数组与指针的糅合
1.         如程序清单6. 8所示,输出会是什么?
程序清单6. 8  数组与指针的糅合应用1
int arr[] ={6,7,8,9,10};   
int *ptr   =arr;  
*(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 。
其实这个题目还有一个延伸,int *ptr = (int *)( (int) a + 1 ), *ptr是多少。答案是:2 00 00 00。
假设 &a[0] = 0x1000 0000,由于存储方式是小端模式,那么a[0] = 1和a[1] = 2的存储方式如图6. 2所示。
因为a = 0x1000 0000,而(int)a将这个地址强制转化为了int型数据,((int)a + 1) = 0x1000 0001,经过(int *)((int)a + 1)成了地址,ptr = (int *)((int)a + 1),由于ptr是指向int型的指针,*ptr占4个字节,*ptr所占字节即为:0x00,0x00,0x00,0x02,那么*ptr即为0x02000000。
092644jwsb1ozwqq91pj9l.jpg.thumb.jpg
3.         如程序清单6. 10所示,如果ptr1为0x1000 0000,那么三个输出分别为多少?
程序清单9. 10  数组与指针的糅合应用3
int iArray[7] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 } ;
int *ptr1     = iArray ;
int *ptr2     = &iArray[5] ;

printf( " ptr2       = %#x \n" , ptr2 ) ;
printf( " ptr1       = %#x \n" , ptr1 ) ;

printf( "ptr2 - ptr1 = %d \n" , ptr2 - ptr1 ) ;
很明显iArray是整型数据类型数组,那么ptr2 = ptr1 + 5*sizeof(int) = 0x1000 0014。很多同学立马就会脱口而出ptr2 – ptr1 = 20嘛!真的是这样吗?其实答案是:5!
解释之前,我们先来看这个程序:
int iArray[7] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 } ;
char *p1      = (char *) iArray ;
char *p2      = (char *) &iArray[5] ;

printf( "p2 - p1     = %d \n" , p2 - p1 ) ;
这个程序的输出是:20。因为指针类型是char*,char是1个字节;而上面*ptr1和*ptr2是int*,所以答案是:5。
如果是:
short *p1      = (short *) iArray ;
short *p2      = (short *) &iArray[5] ;
则p2 – p1 就是为:10。
这里还有一个延伸:
    int iArray[7] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 } ;
    int *ptr1     = iArray ;
    int *ptr2     = &iArray[5] ;

    printf( " ptr2       = %#x \n" , ptr2 ) ;
    printf( " ptr1       = %#x \n" , ptr1 ) ;

    printf( "ptr2 - ptr1 = %d \n" , (int)ptr2 – (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_ptr;

    for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++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
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     = %#x\n" , ptr1);
    printf(" &a[0]    = %#x\n" , &a[0]);
    printf(" ptr1+1   = %#x\n" , ptr1+1);
    printf(" &a[0]+1  = %#x\n\n" , &a[0]+1);

    printf(" ptr2     = %#x\n" , ptr2);
    printf(" &a       = %#x\n" , &a);
    printf(" ptr2+1   = %#x\n" , ptr2+1);
    printf(" &a+1     = %#x\n\n" , &a+1);

    printf(" ptr3        = %#x\n" , ptr3);
    printf(" &a[0][0]    = %#x\n" , &a[0][0]);
    printf(" ptr3+1      = %#x\n" , ptr3+1);
    printf(" &a[0][0]+1  = %#x\n\n" , &a[0][0]+1);

    return 0;
}
这个题目涉及到两个知识点,其一,讲烂了的数组首元素地址和数组的首地址;其二,数组指针和指针数组的区别。
先看第三个指针,int *ptr3 = &a[0][0];这个毫无疑问,是将数组a的首元素地址赋给指针ptr3,由于是int型数组,那么ptr3+1则是偏移一个int型大小,即偏移4个字节,那么ptr3这一组的输出即为:
ptr3        = 0x22fe70
&a[0][0]    = 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     = 0x22fe70
&a       = 0x22fe70
ptr2+1   = 0x22ff70
&a+1     = 0x22ff70
剩下第一个指针,这个和6.9节差不多,int (*ptr1)[8]    = a ;其实它是等价于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     = 0x22fe70
&a[0]    = 0x22fe70
ptr1+1   = 0x22fe90
&a[0]+1  = 0x22fe90
这里再一次重复讲一下数组指针和指针数组。
int (*p)[8]        p是指向一个含有8个整型数据的数组的指针(数组指针)
int  *p[8]         p是一个含有8个指针型变量的数组(指针数组)
点赞  2013-11-27 19:48
第七节  结构体与联合体


1.1      结构体内存对齐问题
1.         这个程序本是我写来验证结构体内存对齐问题,但是我在linux系统和windows系统下的答案让我有点意外,我便将其加进本书。如程序清单7. 1所示,程序输出会是什么?
程序清单7. 1  结构体的内存对齐问题
#include

struct Date{
    int    year      ;
    int    month     ;
    int    day       ;
} ;

struct DateType{
    int    year      ;
    int    month     ;
    int    day       ;
}birthday ;

struct student{
    int    iNum      ;
    char   cName[30] ;
    float  fScore    ;
    char   cSex      ;
    double menber    ;
} people ;

int main( int argc , char *argv[] )
{
printf( "sizeof(struct Date)     = %d \n\n",
sizeof( struct Date)     ) ;   
printf( "sizeof(struct DateType) = %d \n"  ,
sizeof( struct DateType) ) ;
    printf( "sizeof(birthday)        = %d \n\n", sizeof( birthday )       ) ;
    printf( "&birthday.year          = %d \n"  , &birthday.year           ) ;
    printf( "&sizeof.month           = %d \n"  , &birthday.month          ) ;
    printf( "&sizeof.day             = %d \n\n", &birthday.day            ) ;

printf( "sizeof(struct student)  = %d \n"  ,
sizeof( struct student)  ) ;
    printf( "sizeof(people)          = %d \n\n", sizeof( people  )        ) ;
    printf( "&people.iNum            = %d \n"  , &people.iNum             ) ;
    printf( "&people.cName           = %d \n"  , &people.cName            ) ;
    printf( "&people.fScore          = %d \n"  , &people.fScore           ) ;
    printf( "&people.cSex            = %d \n"  ,
&people.cSex             ) ;
    printf( "&people.menber          = %d \n\n", &people.menber           ) ;
    printf( "sizeof(people.menber)   = %d \n\n", sizeof( people.menber  ) ) ;

    return 0 ;
}
传统在windows下,结果大家都应该知道,我现在就直接把window下和linux下结果直接贴出来,大家看看。(如果大家对结果有质疑,大可上机试试,毕竟眼见为实。)
sizeof(struct Date)      = 12

sizeof(struct DateType) = 12
sizeof(birthday)          = 12

&birthday.year            = 4210832
&sizeof.month             = 4210836
&sizeof.day               = 4210840

sizeof(struct student)  = 56
sizeof(people)           = 56

&people.iNum            = 4210848
&people.cName           = 4210852
&people.fScore          = 4210884
&people.cSex            = 4210888
&people.menber          = 4210896

sizeof(people.menber)   = 8

请按任意键继续. . .
上面是C-Free中运行的结果,你可以试试VC等,答案依然如此。

我们再来看看linux下结果:
root@zhuzhaoqi-desktop:/home/zhuzhaoqi/C/prog1.34# ./prog
sizeof(struct Date)     = 12

sizeof(struct DateType) = 12
sizeof(birthday)        = 12

&birthday.year          = 134520948
&sizeof.month           = 134520952
&sizeof.day             = 134520956

sizeof(struct student)  = 52
sizeof(people)           = 52

&people.iNum            = 134520896
&people.cName           = 134520900
&people.fScore          = 134520932
&people.cSex            = 134520936
&people.menber          = 134520940

sizeof(people.menber)   = 8
这是linux下编译的结果。
加粗标注区域够让你吃惊吧!
说实话,看到第一眼,我也傻了。为什么,我们再看下划线标注区域,people.cSex 在windows下联系上下确实应该占用8个字节,可是,可是为什么在linux下只占用4个字节!!
原来,在linux中以4个字节为开辟单元,即不足4个开辟4个,多于4个的继续开辟4个,多出的部分 放进另一个4个字节中。
struct student{
    int    iNum      ;    /* 开辟4个字节 */
    char   cName[30] ;  /* 开辟32个字节 */
    float  fScore    ;   /* 开辟4个字节 */
    /*开辟4个字节,自己用1个字节,剩下3个,不足以存储menber */
char   cSex      ;   
    double menber    ;  /* 所以这里重新开辟4+4个字节 */
} people ;

所以我们得出的答案是:4+32+4+4+8=52。
但是,我们一直使用的windows下,以最大单元为开辟单位,即系统先检查结构中最大单位 为double 8个字节,所以以8个字节为单位。
student 在Linux和windows下内存开辟如图7. 1和图7. 2所示。

095925h0k444zcxxryddc3.jpg.thumb.jpg
1.1      结构体在STM32的应用
1.         如程序清单7. 2所示程序是截取STM32固件库中的一段代码,请问输出是什么?
程序清单7. 2  结构体在STM32的应用
#include
typedef   volatile unsigned int  vui32;
typedef struct {
  vui32  CRL;
  vui32  CRH;
  vui32  IDR;
  vui32  ODR;
  vui32  BSRR;
  vui32  BRR;
  vui32  LCKR;
} GPIO_TypeDef;

#define  GPIOA       (GPIO_TypeDef *)0x10000000
#define  GPIOLED     (GPIO_TypeDef *)GPIOA

void 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   =0x10000000
GPIO->CRL  = 0x10000000
GPIO->CRH  = 0x10000004
GPIO->LCKR = 0x10000018
请按任意键继续. . .
确实很巧妙,方便!

1.2      结构体与指针
1.         已知如下所示条件。
struct student{

    long int    num    ;
    char        *name   ;
    short int  date     ;
    char        sex      ;
    short int  da[5]    ;

}*p;
p = (student*)0x1000000 ;
那么请问,以下输出什么?
printf( "sizeof(*p)        = %d\n"  , sizeof(*p)        ) ;
printf( "sizeof(student)  = %d\n"  , sizeof(student)     ) ;
printf( "p                   = %#x\n" , p              ) ;
printf( "p + 0x200         = %#x\n" , p + 0x200        ) ;
printf( "(char*)p + 0x200       = %#x\n" , (char*)p + 0x200      ) ;
printf( "(int*)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      联合体的存储
095939ee8k4na9rkune4eu.jpg.thumb.jpg
1.         如程序清单7. 3所示,程序输出什么?
程序清单7. 3  联合体的存储
union {
    int i ;
    struct {
       char L;
       char H;
    }Bity;
}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 = (65536-10000)/256;
TLx = (65536-10000)%256.

那么我们就可以让这样写这个程序
union {
    unsigned int i ;
    struct {
       unsigned char H;
       unsigned char L;
    }Bity;
}N;

int main(int argc, char* argv[])
{
    ……
    N.i = 65536 - 10000;
THx = N.Bity.H ;
TLx = N.Bity.L ;
……
    return 0;
}
这样很方便并且高效地将高低位置好,其实单片机是一个很好学习C语言的载体,麻雀虽小,但是五脏俱全。
65536 – 10000 = 55536 = 0xD8F0。
由于在单片机中,int是占2字节,那么存储方式如图7. 4所示。
095939ma3t2tdkptfauttt.jpg.thumb.jpg

1.1      结构体在联合体中的声明
1.   如程序清单7. 4所示,请问:printf( "%d\n" , sizeof(T.N) ) ;
printf( "%d\n" , sizeof(T)    ) ;输出什么?
程序清单7. 4  结构体在联合体中的声明
union T {
    int i ;
    struct N {
       int    j ;
       float  k ;
       double m ;
    };
};
第一个结构体嘛,4+4+8=16;第二个嘛,最大元素所占内存开辟,则为:16!真的是这样吗?正确答案是:16,4!
union T {
    int i ;
    struct N {
       int    j ;
       float  k ;
       double m ;
    }A;
};
如果这个程序是这样,输出又是多少呢?正确答案是:16,16!不知道你是否知道其中的原因了呢?

1.2      结构体与联合体的内存
1.   如程序清单7. 5所示,语句printf("%d",sizeof(struct date)+sizeof(max));的执行结果是?
程序清单7. 5  结构体与联合的内存
typedef union {
long i;
int k[5];
char c;
} DATE;

struct data {
int     cat;
DATE    cow;
double dog;
}too;

DATE max;
很明显,这里考查的是联合体和结构体的内存对齐问题。联合体的内存大小是以最大类型存储内存作为依据,而结构体则是内存对齐相加。
上面例子的联合体最大是:20,所以sizeof(max) = 20 ;在结构体中,sizeof(cat) = 4 ,sizeof(cow) =  20 ,sizeof(dog) = 8 ,由于内存都是对齐的,所以siezof(struct date) = 32 .所以最终答案是:52.
#include

typedef  union {

    long int  i    ;
    int       k[5] ;
    char      c    ;

}DATE ;

struct  data {

    int     cat ;
    DATE    cow ;
    double  dog ;

}too ;


int main(int argc, char *argv[])
{

    DATE max;

    printf("sizeof(cat)=%d\n",   sizeof(too.cat));
    printf("sizeof(cow)=%d\n",   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)=8

sizeof(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;
    unsigned char c;
}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.c和u.s是在同一个地址空间中,那么u.s中存储的数据即为100。100转化为二进制为:0110  0100。由于是小端模式存储方式,那么u.s.c1取最低三位100,即为十进制的4;u.s.c2取中间三位100,即为十进制的4;而u.s.c3取最高两位01,即为十进制的1。所以输出即为:4,4,1。
095942b2vwygx6d6bybts6.jpg.thumb.jpg
u.c和u.s的存储方式如图7. 5所示。
点赞  2013-11-27 19:54
第八节  内存分配与内存释放


1.1      malloc
1.       32 位系统下, C++程序,请计算 sizeof  的值 。
char str[] = “http://www.ibegroup.com/” ;
char *p = str ;
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函数会有什么样的结果?
程序清单8. 1  malloc()的应用1
void 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函数会有什么样的结果?
程序清单8. 2  malloc()的应用2
char *GetMemory(void)
{
    char p[] = "hello world";
    return p;
}

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函数会有什么样的结果?
程序清单8. 3  malloc()的应用3
void 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所示,请问这个会是输出什么?
程序清单8. 4  malloc()的应用4
#include
char *str()
{
    char *p = "abcdef";
    return p;
}
int main(int argc, char *argv[])
{
    printf("%s", str());
    return 0;
}
乍眼一看,在哪里见过?是的,确实似曾相识。有记忆了吧,会不会有人立马说出答案:输出乱码?
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
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的。
点赞  2013-11-27 19:55
第九节   笔试中的几个常考题

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 = strDest;
    while( (*strDest++ = * strSrc++) != ‘\0’ )
    NULL ;
    return address ;
}
这个题目创维和华为都曾用来做为考题。
在程序开头我们肯定要断言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 = to;
    while( count != 0 )
    {
        *address++ = *from++ ;
        if ( *from == '\0')
        {
           *address++ = '\0' ;
        }
        count-- ;
    }
    return address ;
}
1.2      CPU的使用率
1.   写一个程序,让你的电脑CPU使用率一直运行在50%。
#include
#include
using namespace std;
/* 让CPU的使用率在50% */
int main(int argc, char *argv[])
{
    for( ; ; )
{

       for( int i = 0 ; i < 800000000 ; i++ ) ;

       _sleep(10) ;

    }

    return 0;
}
这里的800000000是根据我自己电脑算出来的,因为我的电脑主频是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) ;

    }
    return countx ;
}
如果x大于0,那么x一定有一位为1,所以进入while之后countx先加1。如果x=100,那么经过x=x&(x-1),x为0,countx为1,此时结束程序。
1.4      二进制高位到低位实现逆变
1.   编写一个函数,实现将一个32位int型数据的二进制高位到低位实现逆变,例如:1101 0101变成1010 1011。
这个题目的解决方法很多,代表性的有两种。
int func(unsigned int uiData , int length)
{
    unsigned int uiValue = 0 ;
    int            i        = 0 ;
    for  ( i = 0 ; i < length ; i++ )  
{
       uiValue  = (uiValue << 1) + (uiData & 0x01) ;
       uiData   = uiData >> 1 ;
    }
    return uiValue ;
}
这个方法比较常见,通过移位的方法将高位到低位实现逆序。但是这个方法存在唯一的不足之处是效率低,要进行32次移位和运算。
int func (unsigned int uiData)
{
    unsigned int uiValue = 0 ;

    /* 分而治之的思想 */
    /* 高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)&0x33333333) |
((uiValue << 2)&0xcccccccc);

/*2位中的高低位互换*/
    uiValue = ((uiValue >> 1)&0x55555555) |
((uiValue << 1)&0xaaaaaaaa);

    return uiValue ;
}
这个程序只需要位操作5次,就能实现高位到低位的逆序。我们逐句程序分析一下。假设uiData = 1100 0101 0101 1100 1100 0101 0101 1111。执行完成下面这句程序,
/* 高16位和低16互换 */
uiValue = ((uiData >> 16)&0x0000ffff) | ((uiData << 16)&0xffff0000);
得到1100 0101 0101 1111 1100 0101 0101 1100,也就是高16位和低16位互换。

执行完成:
/*高低16位中的高低8位互换*/
uiValue = ((uiValue >> 8)&0x00ff00ff) | ((uiValue << 8)&0xff00ff00);
得到0101 1111 1100 0101 0101 1100 1100 0101,也就是高低16位中高8位和低8位互换。

执行完成:
/*8位中的高低4位互换*/
uiValue = ((uiValue >> 4)&0x0f0f0f0f) | ((uiValue << 4)&0xf0f0f0f0);
得到1111 0101 0101 1100 1100 0101 0101 1100,也就是从高位起,每8位段的高4位和低4位完成互换。

执行完成:
/*4位中的高低2位互换*/
uiValue = ((uiValue >> 2)&0x33333333) | ((uiValue << 2)&0xcccccccc);
得到1111 0101 0101 0011 0011 0101 0101 0011,也就是从高位起,每4位段的高2位和低2位完成互换。

执行完成:
/*2位中的高低位互换*/
uiValue = ((uiValue >> 1)&0x55555555) | ((uiValue << 1)&0xaaaaaaaa);
得到1111 1010 1010 0011 0011 1010 1010 0011。也就是从高位起,每2位段的高1位和低1位完成互换。和原始数据1100 0101 0101 1100 1100 0101 0101 1111进行对比,逆序。

1.5      大小端测试
1.   编写一个函数,测试MCU是大端模式存储还是小端模式存储。
/****************************************************************
**  函数名称:LBEndian
**  函数功能:大小端测试函数
**  入口参数:None
**  出口参数:1 or 0
****************************************************************/
int LBEndian (void)
{
    unsigned int   uiNumber = 0x12345678 ;
    unsigned char  *ucptr   = (void *)0  ;

    /* 将最低位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 iMid;

    while (iLow <= iHigh)
{
       iMid = (iHigh + iLow)/2;
       if (iArray[iMid] > key)
{
           iHigh = iMid - 1 ;
       }  
else if (iArray[iMid] < key)
{
           iLow = iMid + 1 ;
       }  
else
{
           return iMid ;
       }
    }
}
数据结构中的各种查找算法在笔试中是无处不在,在工程应用中也是“无孔不入”。所以作为一个软件工程师,必须牢牢掌握各种查找算法。

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  ) () ;
因为内存是摸不着,猜不透的,所以很像地雷阵,随时都可能挂掉。
点赞  2013-11-27 19:56
第十节  数据结构之冒泡排序、选择排序

我相信很多人曾经写冒泡排序和选择排序都是一个算法一个代码,并且还一个一个
写得不亦乐乎。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 uiTemp;

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, j;
int iNoSwapFlg = 0;
void *pvThis = NULL;
void *pvNext = NULL;

/*
  *  冒泡排序
  */
i = stAmount - 1;
do {
  
  iNoSwapFlg = 0;
  pvThis = pvData;
  pvNext = (char *)pvData + stSize;
  j = i;
  
  do {
   if (CompareFun(pvThis, pvNext) > 0) {
    __swap(pvThis, pvNext, stSize);
    iNoSwapFlg = 1;
   }
   pvThis = pvNext;
   pvNext = (char *)pvNext + stSize;
  } while (--j != 0);
  
  if (iNoSwapFlg == 0) {
   break;
  }
} 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 + stSize;
所以无论如何,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 = pvData;
  pvNext = (char *)pvData + stSize;
那么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 = pvNext;
pvNext = (char *)pvNext + stSize;
这样以此类推。
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  , k  ;
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 < stAmount  ; i++ ) {
  
  k = i ;
  for ( j = i + 1  ; j < stAmount ; j++) {
         
   pvThis  = (char *)pvData + j*stSize;         
              pvThat  = (char *)pvData + k*stSize;
   if (CompareFun(pvThis  , pvThat  ) > 0) {
            
    k = j ;
   
   }
   
   if( k != i ) {
   
    pvThis  = (char *)pvData + i*stSize;         
                  pvThat  = (char *)pvData + k*stSize;                  
   
    __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'
请按任意键继续. . .

测试通过!
点赞  2013-11-27 19:57
第十一节   机试题之数据编码
某部队为了防止消息泄密从而对原始数据进行编码,编码规则如下。
1)        所有信息都为ASCII 编码;
2)        在收到原始密文后将字符进行二进制逆转,如字符'A'(0x41,0100 0001B)将数据逆转后为(0x82,1000 0010B);
3)        将逆转后的数据按照16 进制打印输出(原始数据允许空格),如字符串"ABCD EFGH"加密后的输出结果为:"8242C22208A262E212"。
为了加快编码解码速度现在需要你编写一个程序实现该密文的编码。
这个题目说到底就是将一个字符转化成二进制,再将这个二进制的高低位逆转,之后输出逆变后对应数据的ASCII。
二进制高低位逆转在12.4有详细讲解,为了算法不重复,这里采用逐位逆转方法进行解答。
// text.cpp : 定义控制台应用程序的入口点。

#include "stdafx.h"
#include

/****************************************************************
**  函数名称:  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 uiSum ;
}

/****************************************************************
**  函数名称:  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 = s ;
    }

    /* 以十六进制输出字符串数据 */
    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
4142434445464748
8242c222a262e212
请按任意键继续. . .
点赞  2013-11-27 19:57
第十二节  机试题目之十进制1~N的所有整数中出现“1”的个数

给定一个十进制数N,写下从1开始到N的所有整数,然后数一下其中出现的所有“1”的个数,比如:
1)   N = 2 ,写下1、2,这样只出现1个“1”;
2)   N= 12 ,我们会写下1、2、3、4、5、6、7、8、9、10、11、12,这样1的个数为5。
问题是:写一个函数f(N),返回1~N之间出现“1”的个数,比如:f(12) = 5。

这个题目带有几分找规律性质。本题解法可能较多,这里提供两种。
方法一:
// text.cpp : 定义控制台应用程序的入口点。

#include "stdafx.h"

/****************************************************************
** Function name:      oneNumber
** Descriptions:       计算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           = uliNumber ;
    /*  记录1的个数 */
    unsigned long int uliCount           = 0 ;   
    /*  提取位的权值 */
    unsigned long int uliFlag            = 1 ;
    /*  提取位 */
    unsigned int      uiFlag              = 0 ;
    /*  提取位的幂次方 */
    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 uliCount ;

}

/****************************************************************
** Function name:      main
** Descriptions:       输入输出
** input parameters:   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:      oneNumber
** Descriptions:       计算1的个数
** input parameters:   uliNumber:输入的数据,即为要计算1的个数的数据
** output parameters:  void
** Returned value:     uliCount:1的个数
****************************************************************/
unsigned long int oneNumber ( unsigned long int uliNumber )
{
    /*
    **  记录1的个数
    */
    unsigned long int uliCount           = 0 ;   
    /*
    **  提取位左边的数
    */
    unsigned long int uliLeft            = 0 ;
    /*
    **  提取位右边的数
    */
    unsigned long int uliRight           = 0 ;
    /*
    **  提取位,此位对1进行计数
    */
    unsigned long int uliFlag            = 0 ;
    /*
    **  提取位的权值
    */
    unsigned int      uiFlag             = 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 * uiFlag ;
       } break ;
       /*
       ** 如果提取位为1,那么1的个数等于提取位左边数乘以取提取位的数据
       ** 再加上(0到提取位右边数)+1
       */
       case 1  :
{
           uliCount += uliLeft * uiFlag + uliRight + 1 ;
       } break;
       /*
       ** 如果提取位大于1,那么1的个数等于(提取位左边数+1)乘以
       ** 取提取位的数据
       */
       default :
{
           uliCount += ( uliLeft + 1 ) * uiFlag ;
       } break;

       }
       /*
       ** 提取位向左移动一位
       */
       uiFlag *= 10 ;
    }
    /*
    **  返回1的个数
    */
    return uliCount ;

}

/****************************************************************
** Function name:      main
** Descriptions:       输入输出
** input parameters:   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;
}
点赞  2013-11-27 19:58
第十三节  机试题之  遍历单链表一次,找出链表中间元素

单链表最大的特点就是“不走回头路”,不能实现随机存取。如果我们想要找一个数组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         iData;
    _list_node *next;
}ListNode;

ListNode *FindList(ListNode *head)
{
    ListNode *p1, *p2;

    if ( (NULL == head) || (NULL == head->next) )
    {
       return head;
    }

    p1 = head;
    p2 = head;

while (1)
    {
       if ( (NULL != p2->next) && (NULL != p2->next->next) )
       {
           p2 = p2->next->next;
           p1 = p1->next;
       }
       else
       {
           break;
       }
    }
    return p1;
}

点赞  2013-11-27 20:00
第十四节  机试题之全排序

写一个程序,对任意一串字符串进行全排序。如123的全排序为:123,132,213,231,312,321.
这个题目使用数学很简单,因为高中的排列组合一个式子就把这个题目给KO了,C语言其实也很简单,无非是列举出所有排列顺序罢了。
// text.cpp : 定义控制台应用程序的入口点。

#include "stdafx.h"
//读者在这里思考两个问题:
//1.这个函数的作用是什么?
//2.inline是做什么用的?
inline void Swap(char *a, char *b)
{
    /* 交换a和b */
    char temp = *a;
    *a = *b;
    *b = temp;
}

//读者想想如何列举出所有的排列顺序
void Perm(char list[], int k, int m)
{
    /* 生成list [k:的所有排列方式 */
    int i = 0;
    /* 输出一个排列方式 */
    if (k == m)
    {
       for (i = 0; i <= m; i++)
       {
           putchar(list);
       }
       putchar('\n');
    }
    else
    {
       /* list[k:有多个排列方式 */
       /* 递归地产生这些排列方式 */
       for (i = k; i <= m; i++)
       {
           Swap (&list[k], &list);
           Perm (list, k+1, m);
           Swap (&list [k], &list );
       }
    }

}

//测试
int main(int argc, char* argv[])
{

    char s[] = "1234";
    Perm(s, 0, 3);
    return 0;
}

编译结果:
1234
1243
1324
1342
1432
1423
2134
2143
2314
2341
2431
2413
3214
3241
3124
3142
3412
3421
4231
4213
4321
4312
4132
4123
请按任意键继续. . .
点赞  2013-11-27 20:00
第十五节  机试题之大整数加法运算

所接触的数据类型中,int数据类型、float数据类型等都有范围,如果超出这个范围,则无法表达,更不能进行数学中的运算。但是字符串的长度却不受限制,但是要让字符串进行数学运算也就解决了大整数的运算。
这样的话,可以联想到小学的竖式加减乘除的方法,逐位相加减。
大整数的加法算法:
/****************************************************************
**  函数名称:BigNumberAdd
**  函数功能:大整数的加法运算
**  入口参数:str1:第一个加数
              str2:第二个加数
             ptr:容纳两数之和的空间首地址
             ptrSize:此空间大小
**  出口参数:
****************************************************************/
int BigNumberAdd(const char *str1, const char *str2,
char *ptr, int ptrSize)
{
    /*
    **  iStr1Len:存储第一个字符串
    **  iStr2Len:存储第二个字符串
    **  iMaxLen : 两个字符串中最长的长度
    **  i、j     : 循环
    **  iCarry  : 进位标志位
    */
    int   iStr1Len , iStr2Len , iMaxLen , i , j , iCarry = 0 ;
    char  character1 , character2 ;

    /* 测量两个字符串长度¨¨ */
    iStr1Len = strlen(str1) ;
    iStr2Len = strlen(str2) ;

    /* 将ptr存储区域的数据全部清零 */
    memset(ptr, 0, ptrSize) ;

    /* 得到两个加数中最大的长度¨¨ */
    iMaxLen = iStr1Len > iStr2Len ? iStr1Len : iStr2Len ;

    /* 从低位向高位逐位相加 */
    for ( i = 0 ; i < iMaxLen ; i++ ) {
       character1 = \
(iStr1Len - 1 - i) < 0 ? '0' : str1[iStr1Len - 1 - i] ;
       character2 = \
(iStr2Len - 1 - i) < 0 ? '0' : str2[iStr2Len - 1 - i] ;
       /* 如果character1和character2不是数字,则退出 */
       if ( (!isdigit(character1)) || (!isdigit(character2)) ) {
           return 0 ;
       }

/* 模仿竖式逐位相加 */
       iCarry += (character1 - '0') + (character2 - '0') ;
       assert(i < ptrSize) ;
       /* 保存当前位数据 */
       ptr  = iCarry % 10 + '0' ;
       /* 保存进位数据 */
       iCarry /= 10 ;
    }

    /* 如果最高位出现进位,则增加一位 */
    if (0 != iCarry) {
       assert(i < ptrSize) ;
       ptr[i++] = iCarry + '0' ;
    }

    assert(i < ptrSize) ;
    ptr = '\0' ;

    /* 将数字逆序输出 */
    for ( j = 0 ; j < --i ; j++) {
       char cTemp  = ptr[j] ;
            ptr[j] = ptr ;
            ptr = cTemp  ;
    }

    return 1 ;
}

大整数的减法、乘法、除法运算都可依据小学的竖式运算方法。测试结果:
数据一:987654321123456789
数据二:123456789987654321

两数相加之和:1111111111111111110

两数相加之差:864197531135802468

两数相加之积:120408474453741807546258192212924859

请按任意键继续. . .
点赞  2013-11-27 20:00
if (iNumber == 10) {
   i ++;
}
这个语句没问题,当iNumber与10相等,i则++。
但是,问题的关键在于你是不是每次都会记得是:
iNumber == 10,你会不会有那么一次写成了:
iNumber = 10呢?该死的编译器这个时候不会告诉你这是一个错误。
所以有那么一次,程序是不是就铁定挂了。
为了避免不必要的麻烦:
if (10 == iNumber) {
   i ++;
}
这样写,招聘人员一定会眼前一亮。
点赞  2013-11-27 20:01
或许很多学生在大学所学习的C语言只是一个初概念,没有深入理解。今年5月份回校办理毕业手续,给几位学弟交流了下,C语言学了2年还是一团雾水。但是C语言是嵌入式的灵魂(我是这么理解的),如果C语言没有学好,很难写出一个精美的程序。
    或许很多学生知道:
   int  iArray[10];
   int i;
   iArray是什么,但是我问他i[iArray]是什么?就不知道了。为什么,因为对数组和指针理解不够深入。
   iArray = *(iArray+i) = *(i+iArray) = i[iArray],说白了这里就是小学的加法交换律。但是就是因为不理解指针。
   马上就要进行14届应届毕业生的招聘会了,笔者就尽自己所能,每天为《攻破C语言笔试与机试难点》写一点吧。
点赞  2013-11-27 20:01
挺好,呵呵 很基础的,多看看细节对c还是有帮助的
点赞  2013-12-3 22:16
马上找工作了  正好用
点赞  2016-5-23 20:52
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复