[C语言]易错知识点、小知识点复习(1)

时间:2023-12-30 08:06:08

1. 计算机只能识别由0和1组成的二进制指令,需要将用高级语言(如C、C++)编写的源程序(.c、.cpp)编译成二进制目标文件(.obj)。一个程序可以根据需要写在不同的文件里,编译是以文件为单位进行的,如果程序由两个文件组成,那么编译后就得到了两个目标文件。连接的作用就是将所有的目标文件和系统提供的类库相连接,组成一个可直接执行的二进制文件(.exe),这就是最后可以执行的程序。(想想为什么在程序开头#include<math.h>,就可以在程序中调用数学函数了,是因为“连接”时,将数学库函数math.h和自己编写的程序连接在一起了,共同组成一个程序)

2. 编译时会对源程序进行词法检查和语法检查

3. 一个语句可以写在多行,一行可以写多个语句,语句以分号结束(#define宏定义的语句不适使用分号结束)

4. main()函数的函数体可以为空,如:void main(){ }

5. C程序总是在执行完main()函数的最后一条语句后结束[错]。如果程序运行崩溃,就执行不到最后,就退出了

6. 编译的基本单位是文件,文件的基本组成单位是函数(想一想编写的一个.c或.cpp文件里,除了#include头文件、#define宏定义、声明的全局变量和函数,就是一个个函数了(main函数也是函数))

7. 函数不能嵌套定义,只能嵌套调用(递归)

8. 常量:①十进制:数学上的数字 ②八进制:以0开头,由0-7数字组成,如012表示十进制数字10 ③十六进制:以0x开头,由0-9数字和a-e(A-E)字母组成,如0x2a表示十进制数字42(注意是数字0开头,不是字母O!)

9. 十进制与二进制、八进制、十六进制的互换

      [C语言]易错知识点、小知识点复习(1)

10. e或E之前必须要有数字,e或E之后必须要有整数数字。如1e2[√],e3[×],2.4e3[√],3e2.4[×]

11. 转义字符可能包含两个或多个字符(如\n,\12),但它只表示一个字符(\n是一个字符,起到换行的作用,\12表示十进制ASCII码为10的那个字符)。编译系统见到字符’\n’时,会接着找它后面的字符,把反斜杠(\)和其后字符当作一个字符,在内存中只占一个字节

12. 反斜杠后面加数字的情况,有两种。(\0表示空字符,这里不考虑它了)

  ①\ddd :ddd是一个数的八进制表示,\ddd所对应的字符是ASCII码值的八进制表示为ddd的那个字符。如\12,这里12是八进制形式,它对应的十进制是10,所以\12就表示ASCII码为10的那个字符。

  ②\xhh:hh是一个数的十六进制表示,\xhh所对应的字符是ASCII码值的十六进制表示为hh的那个字符。如\x12,这里12是十六进制形式,它所对应的十进制是18,所以\x12就表示ASCII码为18的那个字符。

  问:1.怎么知道反斜杠后面的数字是什么进制?

  反斜杠\后面不能直接加十进制,如果加的是十六进制,在反斜杠后面要加上x,这是一个标志。所以如果是\12,则说明这是八进制;如果是\x12则是十六进制。

  所以\18这种写法就是错的,没有x这标志,18就只能是八进制形式,但又出现了8,所以矛盾,错误。

13. 字符要在单引号之间(‘ ‘),如果想表示一个单引号,需要这样写:\’   如果想表示一个反斜杠,需要这样写:\\

14. 标识符命名以字母或下划线_开头,由字母、下划线和数字组成。即开头第一个字符不能是数字,标识符长度不能超过255个字符

15. 关于自增、自减运算

  i++是先进行运算,然后i递增1;++i是i先递增1,然后参与运算。注意这里i++或++i的这条性质是对于i++或++i与别的式子放在一起时来说的,如果表达式中只有i++或++i这一个式子,那就没有这个区别。如在for(表达式1;表达式2;i++)和for(表达式1;表达式2;++i)的作用是一样的。

  自增运算符(++)和自减运算符(--)必须作用于变量,不能对常量进行。因为i++等价于i=i+1,是个赋值表达式。而赋值表达式左边的值(称为左值)不能是常数或表达式,只能是变量。

  如int i=2,i++之后i=3或者++i之后i=3;但是不能写成这样:(i++)++。这种形式是错的,因为i++之后是3,是个常数,不能再进行++;同样的,i--/=5这种写法也是错的。

16. 强制类型转换的格式

  (数值类型)变量,如:int i=2,想把i变成float型,需要这样写:(float)i

17. C语言本身不提供输入输出语句,printf(),scanf()是stdio.h头文件中提供的

18. 混合赋值表达式要注意括号的问题。

  如a*=b+c等价于a=a*(b+c)而不是a=a*b+c,要注意这一点

  int a=0,m=3,k=15   则a=++m*k+m运算后,a=64,m=4,k=15.

  赋值表达式先计算赋值运算符右边表达式的值,再把这个值赋值给左边的变量a。要注意表达式++m*k+m是从左往右算的,m++之后m的值已经改变了,第二个m的值已经是改变后的值了

19. 数学式3xy/5ab,变量x,y为整型,a,b为浮点型,C程序中对应的正确表达式为:

  A.3/5*x*y/a/b  B.3*x*y/5/a/b  C.3*x*y/5*a*b  D.3/a/b/5*x*y

  选D。A中3/5为0,整个式子就等于0了;B中3*x*y都是整数,再除以5是整除,而不是数学意义上的除法;C和B一样;D中3/a为整数除以小数,在C语言中结果为小数,正确

20. int整型在有的编译器里分配4个字节(如Visual C++),有的分配2个字节。如果题目告诉sizeof(int)=2,则说明分配了2个字节

  VC中,int占4个字节,数值范围为(-2^31,2^31-1);short占2个字节,数值范围为(-2^15,2^15-1);char占1个字节,数值范围为(-2^7,2^7-1)。这涉及到原码、反码和补码的知识,一个字节是8个二进制位,一个二进制位只能表示0或者1这两个数字

21. unsigned int存储的正数范围比[signed] int几乎大了一倍

22. 把一个字符赋值给一个字符变量,并不是把该字符本身放到内存中去,而是把这个字符所对应的ASCII码的二进制形式放到内存单元中。字符变量和整型变量是可以通用的,是互相兼容的,可以相互赋值,也可以进行算术运算。在printf()中%d输出整数,%c输出字符。但是要注意字符变量和整型变量能用的字节数是不同的,相互赋值或运算可能会导致溢出或截断

23. 字符串常量大小的问题。”abc”是一个字符串常量,它的大小是4,即sizeof(“abc”)=4.这是因为编译系统会在字符串最后自动加一个’\0’作为字符串的结束标志。这里需要和strlen()区分,如:sizeof(“abc”)=4,sizeof(“abc\0”)=5,sizeof(“a\0bc”)=5,strlen(“abc”)=3,strlen(“a\0bc”)=1. 即sizeof()是计算字符串所占的字节数,\0也占一个字节,不管\0在字符串的什么位置,但不管是否自己写出\0,字符串末尾系统都会自动添加一个\0结束符。而strlen()是计算字符串的“有效个数”,即遇到\0就结束判断,且\0这个字符不计数

24. 编译分为预编译和正式编译。#define定义的符号常量虽然有名字,但它是常量不是变量。如#define PI 3.14,在进行预编译时,源程序中的所有PI都被替换成了3.14,正式编译时源程序中已经没有PI这个符号了

25. 关于#define定义的函数代入的问题。

  如:#define f(a) 3*a*a    在main()中有语句:f(3+5),它的结果是3*3+5*3+5=29,而不是3*8*8=192. 这是因为#define定义的宏函数只能进行简单的、不智能的替换,对于它来说3+5只是一个字符串,没有数学上的含义,所以它只能把函数定义中的a换成3+5,而不能自己加个括号,不能满足这一层的逻辑要求

26. 符号常量没有类型,在内存中不存在以它命名的存储单元

27. 强制类型转换时,得到一个所需类型的中间数据,但原来变量的类型不发生变化。如float x=2.4;int y=(int)x;  之后,y=2,但是x还是2.4浮点型,对变量的类型转换不会影响到原来的数据类型

28. 不同类型的整型数据间赋值,按照存储单元中的存储形式直接赋值,所以有可能会发生截断的现象。如unsigned short a; signed int b=-1; a=b;则a的值是65535. 将一个有符号数赋值给一个无符号数,有符号数的第一位原来是表示符号不表示存储大小的,但赋值给无符号数后,这一位就也表示存储空间的大小了。

29. int a=b=0[×],int a,b=a=0[√],这个地方可能容易错

30. 逻辑表达式“自动优化”问题

  C语言中,0表示假,非0表示真,这是从我们的角度来说的。如果一个逻辑表达式,它返回给我们的结果只有两个,一个是0,一个是1。0表示假,1则表示真。根据或(||),与(&&)的特性,如果一个逻辑表达式已经可以判断其真假,那么就不会再继续执行下面没有执行的语句部分。如int a=3,b=4,c=5. ①a||b++,这个逻辑表达式的值是1,a的值是3,但是b的值依然是4而不是5!这是因为在计算这个逻辑表达式时,从左往右看到a,程序读取a的值是3了,就不会继续执行b++这个部分,因为不管||后面是真是假,a||b++都是真的。同样的,如果int a=0,b=4,c=5. ②a&&b++,这个逻辑表达式的值是0,a的值是0,但是b的值还是4,因为当读取a的值是0时,就已经可以判断出这个逻辑表达式的值是假的了,因为0&&任何数都是0.

31. 三个逻辑运算符的优先级为:!> && >||

32. (表达式1,表达式2,表达式3)这样的式子也是一个表达式(表达式是指由运算符和操作数组成的式子,如1+2是算术表达式,2||4是逻辑表达式,3<4是关系表达式,a=2是赋值表达式等等),这个表达式叫作逗号表达式。从左往右计算,计算顺序为:表达式1,表达式2,表达式3,最后这整个逗号表达式的值是表达式3的值。即int x;x=(2,3,4),则(2,3,4)是一个逗号表达式,它的值是4,将4赋给x,x的值也是4. (表达式都是有值的,想想算术表达式1+2的值是3,逻辑表达式2||4的值是1,关系表达式3<4的值是1,赋值表达式a=2的值是2,同样地逗号表达式(2,3,4)的值是4. 这里可能有一个容易疑惑的地方,如果 a=b=3,这是什么意思?赋值表达式从右往左算,所以这个式子相当于a=(b=3),先计算b=3这个赋值表达式的值,再把结果赋给a,a的值也等于3. 这里还要注意a=b=表达式,赋值运算符的右结合性是对于赋值运算符来说的,即a=(b=表达式),但是对于表达式内部,它的结合性由这个表达式自己决定,赋值表达式的右结合性不是说所有的东西都是从右往左的,要分清它针对的是哪一个层面)

33. 对于一个整数,%d输出十进制形式,%o八进制,%x十六进制

34. 关于逗号表达式一个特别容易错的题目!

  int t=1;printf(“%d”,(t+5,t++)). 输出的是1不是2!!因为这里t++要等逗号表达式运算完返回结果后再递增1,而不是t递增1后再返回逗号表达式的结果,有点绕要想清楚。指针里也有一个类似的情形,*p++,它等价于*(p++)。因为指针运算符(*)和自增运算符(++)的优先级相同且结合性都为右结合性,所以p先与++结合,即*(p++),但是这里++是后置自增运算符,它要等其他人执行完毕后才能递增,所以正是因为要先计算p++,才导致了要先取p的值(*p),然后p再递增1,这是由后置自增运算符的性质决定的!(我有一种他们俩互相礼让的感觉,指针运算符说:我俩优先级相同,但你更靠在右边,你先运算吧。 自增运算符说:没错,我是应该先运算,但我是后置自增运算符,我要等你运算结束后才能递增1,所以你赶紧算吧)

35. int n;float x,y;执行语句y=n=x=3.2后,x=3.2,n=3,但y的值是3.0而不是3!这是因为在执行y=n时,系统自动进行了隐式类型转换,y=n相当于y=(float)n

36. scanf(“x=%d”,%x); 这里在键盘输入时要把x=也打进去

37. 关于八进制常量和十六进制常量,我们自己表示这个数时要加上前缀0或0x,但是C语言程序在输出时不会输出这些前缀

38. 判断两个数相等要用(==),而不是(=),后者是赋值运算符

39. switch选择语句中switch的表达式为整型或字符型,case后面要加上整型或字符型常量或常量表达式。break可以加也可以不加,要注意不加break的特殊情况

40. ①int i=10,j=0;

   if(j=0) i++;else i--;

i的值是9,j的值是0;

  ②int i=10,j=0;

  if(j==0) i++;else i--;

i的值是11,j的值是0

  要注意区别这两种情况

41. if-else语句中,else总是与前一个未配对的if组合在一起,而不管写出来的格式是什么样子的。类似地,也要注意不要被花括号骗了,不要想当然地做下去。如:if(a<b) t=a;a=b;b=t;

因为没有花括号,所以这样写不是a和b互换的意思

42. int x=5,y=7,z=8; 执行z+=x++||y++||++z后,x=6,z=9,y=7. 这是因为逻辑表达式有“自动优化”的特性,z的值加了1不是说执行了后面的++z,而是z=z+(x++||y++||++z),z加的1是这个逻辑表达式返回的结果

选择结构和循环结构的循环体可以为空。选择结构的循环体为空,则什么也不做;循环结构的循环体为空,则是死循环

  int a=3,b=5,m;执行表达式m=a<=3&&a+b<8后,m的值是0. 这个式子其实是这样的:m=(a<=3&&a+b<8),因为赋值运算符的优先级比关系运算符要低很多(在C语言中,赋值运算符只比逗号运算符的优先级高)

43. while循环与do while循环的区别

  如:int i=1,j=2;

  while(i<j) { //do something} 和 do {//do something}while(i<j),如果第一次执行时判断关系式都成立那么两种循环的效果是一样的。但如果第一次判断条件就不满足,那么while循环不会执行循环体直接退出,而do while循环会执行一次循环体,也只能执行这一次循环体,然后退出。也就是说do while循环的循环体至少执行一次,而while循环的循环体有可能一次都不会执行

44. 赋值运算符的优先级很低,只高于逗号运算符

  如:int a,b; b=(a=3*5,a*4),a+15,b的结果是60,而不是30

continue与break的区别,continue是退出本次循环,接着进行下一循环的判断;break是直接结束这个循环,不再进行判断。continue和break都是针对内层循环来说的

45. 关于函数参数传递的问题。

  值传递时,形参值改变,不会影响到实参;引用传递时,对形参的修改会影响到实参。即如果想在函数中修改值并能够把这种修改体现到调用函数中(如main函数),就要传递指针(地址)。但是这里如果和指针结合,有一个很经典的问题。

  如:想在函数中交换两个数,传递指针是肯定的,但是在函数中不能交换两个数的地址,而应该直接交换两个地址所对应的值,这样才能真正实现交换。

即void swap(int* pa,int* pb){ int *p;p=a;a=b;b=p}这样是不行的!

而应该是:void swap(int* pa,int* pb){int p;p=*a;*a=*b;*b=p;},这有些绕,但要记住这种情况

46. 函数的参数是从右往左计算的。

  如:int f(int a,int b) { return a+b; } int x=6,y=7,z;

z=f(f(x++,y++),f(--x,y--))运算之后的结果是23。这里有两个需要注意的地方,①函数参数从右往左执行,也就是说先执行f(--x,y--)再执行f(x++,y++)。②后置自增或自减运算符,要先把这个值代入到函数中之后,再递增或递减

47. static 声明的变量在函数结束后值是不会消失的,下次再调用这个函数时,变量的值是累加的,可能会出现这种类型的题

一维数组名等于数组首元素的地址,二维数组名等于数组首行的地址。

  如:int a[10]={0},那么a就是这个数组首元素的地址。int a[2][3]={0},a是这个数组首行的地址,*a才是首行首元素的地址

48. 要记住指针里的重要概念:

  ①a[i][j]=*(*(a+i)+j) ②指针运算符(*)和取地址运算符(&)互为逆运算

要知道表示数组元素的几种形式,如果int a[10]={0},想表示数组中第3个元素。那么①a[2] ②*(a+2) ③int *p=a; *(p+2)