深入理解C语言系列之C语言语法陷阱(考题常设置的陷阱点、必须避免的错误和缺陷类型)

1、赋值与等于
=:赋值运算,a=3;表示的是将3赋值给a变量。
==:比较运算,a==3;表示判断a是否等于3,若等于则返回1,否则返回0。

2、按位与、逻辑与
&&:逻辑与,是一种逻辑运算符,规则是“两真才真,有假的假”。
&:按位与,是一种位运算符,将两组数据转化成二进制,按照位置对应后依次做与运算。
01
详情可以见文章:C语言实用基础(高效快速学习精华、实用语句案例多)十一点的位运算。

3、贪心法

  • 第一点
a---b;等于a-- -b;  //先a-b,再a--
不等于a- --b;  //--b先做自减运算,再a-b

例如:a—b

	int a=3;
	int b=2;
	int c=a---b;
	printf("%d,%d,%d",a,b,c);

结果是:2,2,1
若改成a- --b,则结果是3,1,2

  • 另外一种,由于“*”号是指针的标志,而C语言注释符号又是/*,因此引用指针数据做运算时可能会出现以下陷阱:
y = x/*p;   //表示的是注释,p是注释中的内容
y = x / *p;  //这样表示的才是x除*p指针指向的数据
也可以写成:y = x / (*p);这样更加清晰一些

4、以0开头的数字
如果一个整型常量的第一个数字是0,则该常量会被当做是八进制数,因此10和010代表是分别是十进制的10和8。

5、字符与字符串

  • 单引号引起来的字符代表的是该字符的ASCII码值;
  • 双引号引起来的字符串代表的是一个指向无名数组的起始字符的指针,该数组被双引号之间的字符以及一个额外’\0’(字符串标志)初始化。
  • 在双引号引起来的字符串中,注释符号/*属于字符串的一部分;在注释中出现的双引号“”又属于注释的一部分。
    因此,语句:printf("The world")和以下语句是等价的:
char str[]= {
    
    'T','h','e',' ','w', 'o','r','l','d','\n'};
printf(str);

6、结束分号
一个分号就代表一个语句的结束。
因此

if(x[i] > b);
	b = x[i];
if(x[i] > b){
    
    }
	b = x[i];
if(x[i] > b)
	b = x[i];

前两句是等价的,if和赋值语句是两个独立的语句;而第三句中赋值语句在if中。

7、“悬挂”else
考虑以下程序片段:

if(x == 0)
	if(y == 0)
		error();
else{
    
    
	z = x+y;
	f(5);
}

这段代码中的else看着是与第一个if语句(x==0)并列的,但是实际上else会优先和距离它最近的if语句并列。
因此这里的else执行条件是,y != 0,而不是x != 0。

  • 因此遇到这种情况,想要让else的执行条件成为y != 0,那么可以将以上代码修改为:
if(x == 0)
{
    
    
	if(y == 0)
		error();
}
else{
    
    
	z = x+y;
	f(5);
}

在这里巧用了{}括号,将第二个if语句(y0)包在了第一个if语句里面,因此这时候的else与if(x0) 就成为并列的了。

8、非数组的指针

  • 假设我们要将两个字符串s和t连接组成一个字符串r,我们可以借助库函数strcpy()和strcat():
char *r;
strcpy(r,s);
strcat(r,t);

这样子其实无法实现,因为r所指向的地址还应该有内存空间可供容纳字符串,这个空间应该是以某种方式被分配了的。

  • 所以我们此次给r分配一定的存储空间:
char r[100];
strcpy(r,s);
strcat(r,t);

可以看到,我们分配的大小为100,但是我们无法确保r足够大可以容纳s和t连接之后的字符串。

  • 因此我们可以利用malloc()函数来分配内存:
char *r, *malloc();
r = malloc(strlen(s) + strlen(t));
strcpy(r,s);
strcat(r,t);

这样我们就手动给r开发了一块内存,大小为s和t字符数的和。

  • 不过这样还是不够好,malloc()函数有可能无法提供请求的内存,这样的情况下malloc函数会通过返回一个空指针来作为“内存分配失败”的事件信号。下面是较为完善的代码:
char *r, *malloc();
r = malloc(strlen(s) + strlen(t) + 1);  //需要多分配一个内存空间,以装一个空字符作为字符串结束标志
if(!r)  // 判空语句
{
    
    
	complain();
	exit(1);
}
strcpy(r,s);
strcat(r,t);

free(r);  ///释放暂时申请的内存空间r

9、参数的数组声明

  • 在函数中传递参数时,C语言会自动地将作为参数的数组相地转换为指针声明,也就是说:
int strlen(char s[]){
    
    		;		}

int strlen(char *s){
    
    		;		}

是等价的。

  • 但是这两种是不等价的:
extern char *hello;
extern char hello[];
  • 一个指针参数代表一个数组:
main(int argc, char *argv[]){
    
    	;	}
main(int argc, char **argv){
    
    	;	}

在main函数中的第二个参数就是一个指针参数代表一个数组的情况。

10、避免“举偶法”
在C语言中,容易混淆指针与指针所指向的数据,尤其是处理字符串的时候。

char *p, *q;
p = "xyz";

第二行的赋值就是不正确的,p的值其实是一个地址值,不应该是数据值。

11、空指针并非空字符串
空指针是指指向NULL或者0的指针,如果定义一个空指针,就无法将该指针指向内存中存储的内容,因此也无法进行输出操作:

printf(p);
printf("%s",p);

这两种输出都是非法的。

12、不对称边界
在C语言中,一个拥有10个元素的数组,它的下标范围是从0到9的。那么0就是数组下标的第一个“入界点”(指的是处于数组下标范围以内的点,包括边界点),而10就是数组下标中的第一个“出界点”(指的是数组下标范围以内的点,不包括边界点)。
正因为如此,我们可以这样写:

int a[10], i;
for (i=0; i<10; i++)
	a[i] = 0;

而不这样写:

itn a[10], i;
for(i=0; i<=9; i++)
	a[i] = 0;

13、求值顺序
求值顺序与运算符优先级是不同的两个概念,例如:

a < b && c <d;

该语句中,如果a大于或等于b,则无需再进行c<d的判断了,因为表达式肯定为假。

14、运算符&&、||和!
在有些时候,用按位运算符&、|和~替换掉逻辑运算符&&、||和!,程序看上去还能正常工作,但是实际上这只是巧合所致。
在C语言中,通常约定将0视作假,而非0则都是真。

15、带参宏定义
在C语言的宏定义中,传递的参数是不会自动加括号的,也就是说穿参时只是简单的替换,不会判断加减乘除的优先级。例如这样:

#include <stdio.h>
#define A(a, b) a*b  //简单替换的宏定义
#define B(a, b) ((a)*(b))  //会判断的宏定义 
 
int main()
{
    
    
	printf("A=%d\bB=%d",A(3+7,5), B(3+7,5));	
}

输出的结果分别是:
02

文章参考于文献:《C陷阱与缺陷》[美]Andrew Koening

猜你喜欢

转载自blog.csdn.net/Viewinfinitely/article/details/109741454