欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-10-04 20:14:54
1. 计算机只能识别由0和1组成的二进制指令,需要将用高级语言(如C、C++)编写的源程序(.c、.cpp)编译成二进制目标文件(.obj)。一个程序可以根据需要写在不同的文件里,编译是以文件为单位进行的,如果程序由两个文件组成,那么编译后就得到了两个目标文件。连接的作用就是将所有的目标文件和系统提 ......

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)