第一节 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;
}
第二节 数据存储与变量 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 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<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是输出: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进制) = <v:shape id=_x0000_i1027 style="WIDTH: 40.5pt; HEIGHT: 21.75pt" equationxml=' 121.1 脳<21' type="#_x0000_t75"> (二进制)。数据在电脑中存储都是二进制,这个应该都没有疑问。那么这里的数符为: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。
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。
以此类推,*( (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)。
|
-
图2.1 数据存储方式
-
图2.2 指针加1取整型数据
-
图2.3 指针加1取字符型数据
第三节 数学算法解决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<stdio.h>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 <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 :10N!有7位请按任意键继续. . .网友可以自行验证。第四节 关键字、运算符与语句
1.1 static1. 如程序清单4. 1所示,请问输出i、j的结果?程序清单4. 1 static#include <stdio.h>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 = 1i = 2 j = 1i = 3 j = 1i = 4 j = 1i = 5 j = 1i = 6 j = 1i = 7 j = 1i = 8 j = 1i = 9 j = 1i = 10 j = 1请按任意键继续. . .很多人傻了,为什么呢?是啊,为什么呢?!由于被static修饰的变量总存在内存静态区,所以运行这个函数结束,这个静态变量的值也不会被销毁,函数下次使用的时候仍然能使用这个值。有人就问啊,为什么j一直是1啊。因为每次调用fun2()这个函数,j都被强行置0了。static的作用:(1) 函数体内 static 变量的作用范围为该函数体,不同于 auto 变量,该变量的内存只被分配一次, 因此其值在下次调用时仍维持上次的值;(2) 在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;(3) 在模块内的 static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明 它的模块内;(4) 在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;(5) 在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的static 成员变量。1. 如程序清单4. 2所示,输出结果是什么?程序清单4. 2 for循环#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 Second = 0 Fourth = 0Third = 1 Second = 1 Fourth = 1Third = 2 Second = 2 Fourth = 2Third = 3 Second = 3 Fourth = 3Third = 4 Second = 4 Fourth = 4Third = 5 Second = 5 Fourth = 5Third = 6 Second = 6 Fourth = 6Third = 7 Second = 7 Fourth = 7Third = 8 Second = 8 Fourth = 8Third = 9 Second = 9 Fourth = 9Third = 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. 如程序清单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) = 6sizeof(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) = 24sizeof(b) = 24请按任意键继续. . .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. 如程序清单4. 5所示,运行程序,当显示Enter Dividend: , 输入的是a,按下Enter之后程序会怎么运行?程序清单4. 5 scanf()函数的输入#include<stdio.h>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<stdio.h>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. 如程序清单4. 6所示,请问输出会是什么?程序清单4. 6 scanf()函数的返回值int a , b ;printf( "%d \n" , scanf("%d%d" , &a , &b) ) ;输出输入这个函数的返回值?!答案是:2。只要你合法输入,不管你输入什么,输出都是2。那么我们就要深入解析scanf这个函数。scanf()的返回值是成功赋值的变量数量。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. 如程序清单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++如程序清单4. 9所示程序,输出会是什么?程序清单4. 9 *++ptrint 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。原因请读者分析。<ignore_js_op>-
4.jpg (94.96 KB, 下载次数: 6)
图4.1 ptr++
-