现在不少大学都把C语言作为一门必学的编程语言。

C语言考试呢,并不能决定你的实践能力怎么样,他考的很多点,我们不知道,也可以在写代码时避免,我们举个最简单的例子,运算符优先级,这恐怕是必考的内容了。但实际上我们写代码并不需要背下这个,我们完全可以通过多加几个括号解决问题。但是考试它并不会管这些,考试是别人出的题,所以代码也是它的。而我们也需要一个较高的分数,那么我们应该怎么办呢?

打开网易新闻 查看精彩图片

没错,这篇博客就是为了这个而诞生的。从cggwz个人的刷题经历,总结出易错的一些点,提醒大家在考试中注意,那么废话不多说,我们开始吧!

(再强调一遍:这里大部分是出卷人可能的设误点,而不一定是你自己写代码的易错点)

运算符相关

✔ 自增(减)前后缀

我们以自增为例,前缀和后缀的差别有两点:

优先级不同,后缀优先级更高

返回值不同

虽说优先级不同,但是在两者之间也就逻辑非和按位取反,貌似不会有太大影响。

而重点是返回值,也就是说,++x返回的是x+1,而x++返回的是原来的x。

而x本身的值都会在返回值以后立刻改变,也就是执行这个表达式后立即改变,比如:

#include

int main(){

int x=0,y=2,z=1;

x=z++ + z;

printf("%d",x);

return 0;

这个输出结果就是3,而不是2

✔ 逻辑运算符的短路问题

C语言在计算逻辑运算符的时候是采取短路机制的,也就是说,如果已经可以判断这个表达式的最终结果,那么就不再判断接下来的表达式,相应地,那些表达式也不会被执行。而出题人则喜欢通过放置一些赋值语句在后面,某些学生会因为忽略了短路机制,而误以为会执行,从而出错,我们看个例子:

#include

int main(){

int x=3,y=2,z=1;

y=--z&&--x;

printf("%d",x);

return 0;

这里输出是2吗?

并不是,是3

因为--z返回的值是0,而后面的逻辑运算符是&&,也就是说无论后面一个语句真假与否,这个表达式都是假,所以就会触发短路机制,不再计算后面的++x,从而x的值不会改变。

我们来看个变式:

#include

int main(){

int x=3,y=2,z=1;

y=--z&&--x||--x;

printf("%d",x);

return 0;

相信你现在应该能知道结果了,输出就是2.中间的那个–x并不会因为它在中间而被执行。

✔ 赋值运算符的返回值问题

我们知道C语言中的赋值运算符是有返回值的。

它们的返回值是赋值后被赋值变量的值,而不是赋值符号右边的表达式的值。

比如int a=10;a+=3;这里的+=返回的就不是3,而是13.

这也适用于类型转换问题,比如double a;a=2;这里这个赋值语句的返回值就是2.0,而不是2.

✔ 运算符优先级的问题

考试前必须要掌握的内容之一。

但是显然,我这里不会把一大张表放出来,要是需要看看课本就好了。

这里我是想总结一下这张表,让大家更容易的“记住”这张表。

我们也会顺带地总结一下结合方向。

首先第一梯队,优先级最高的,是一些指代所属关系的运算符,比如[]、->、.。在加上一个圆括号。这个应该很好理解。圆括号如同一个老大哥,它在改变运算顺序这方面具有绝对的话语权,自然会在第一梯队,而这个老大哥也是懂人情的,->、.这类符号就好像是调用父对象里的一个子对象,把人家父子关系拆散恐怕不是一个老大哥应该做的,所以它们也属于第一梯队。它们的优先级是最高的。

接下来第二梯队主要是我们的单目运算符。比如正负号、指针符号、取地址、自加自减之类。它们通常是紧紧贴着它们作用的对象的(单目),那关系亲密地如同情侣。虽然狗粮不好吃,但是不能因为嫉妒就拆散人家呀(谁说的,我就觉得可以),所以它们的优先级也是比较高的,属于第二梯队。

第三梯队就是我们的四大金刚啦——加减乘除,哦不,我们不能忘了我们除法的好兄弟——取余。所以它们五个就是我们的第三梯队,我们可以把它们叫做算术运算符。这五大金刚可是从小学就开始陪伴我们了,自然在考虑完父子情、情侣情之后要考虑它们几个了。

第四梯队是位运算的左移和右移运算符。这两个家伙虽然名气没有上面五大金刚大,但是它们也是可以改变变量的值呢,也可以算得上小金刚了。所以,第四梯队,当之无愧。

第五梯队是关系运算符,也就是表示大于小于不等这类符号。它们虽然也很早就开始陪伴我们,名气也不小,但是毕竟是比较运算的结果,自然要等前两个梯队运算完,才能比较,那就只好屈居它们后面啦。

第六梯队是位运算的按位与、或、异或。理论上它们也是改变数值的呀,为什么不在前面的梯队呢?我们可以看看它们的样子:&、|,正好是我们下面一个梯队的一半,二者的关系情同手足,成功固然美好,但是一份兄弟情,哪是成功可以换来的?它们自然愿意留在兄弟身旁,相依相助。

第七梯队是逻辑运算符,加上唯一一个三目运算符——条件运算符。它们连接的多半是关系运算符的结果,自然就得在关系运算符后面啦!

第八梯队可是一个大部队,赋值运算符。它们当然得殿后啦,它们可是把前面的运算结果保存下来的必经之路。它们就如同慈爱的父母,默默地收拾前面这些孩子算出来的各种数据,并把它们认真地保存下来。我们的父母在我们小时候,不也是这样的吗?

咦?我们好像还漏一个,没错,逗号运算符。存在感太低啦!就放在最后一个吧!(我想你们大部分人也很少用它)。

这样我们的优先级就彻底理清楚啦!

总结一下:

分量、下标运算符、圆括号>单目运算符>算数运算符(五大金刚加两个小金刚)>关系运算符>位运算符(与或异或)>逻辑运算符(含条件运算符)>赋值运算符>逗号运算符

这个总结并不严谨,因为有些运算符并不是在一起的,所以要注意对我说的故事的理解,再结合表看看背背就OK了。

下面再说一下结合方向。

这个还是很好总结的,除了后缀自加自减的所有单目运算符、条件运算符、赋值运算符是右结合,其他的符号全是左结合。

这个还是很好理解的,那些单目运算符通常写在作用对象的左边,自然是从右向左结合,赋值符号也是把右边的数值赋给左边,所以也是从右向左,条件运算符……这么特殊的一个,记一下就好了。

这样我们就成功地解决了这张令人头疼的表。怎么样?不困难吧?

✔ sizeof()的返回值问题

首先强调一下,这个不是函数,虽然它看起来很像。

它运算得到的值是括号内的东西所占的内存大小,它是一个int型的值。所以sizeof()的表达式是int型表达式。

函数相关

✔ 函数声明问题

我们知道C语言里是需要函数声明的,如果函数放在主调函数的后面,就需要在主调函数前进行原型声明。

但是不知道有没有像我一样,由于长期把函数放在主调函数的前面(少打几行字),而在考试时容易忘记声明这件事。其实网上有不少信息学竞赛题的代码,它们绝大多数都是把函数放在主调函数前面,所以这里也算是一个提醒吧。

✔ mian函数参数问题

我们知道main函数有三个参数argc,argv,envp

我们主要提示一下前面两个。

现在如果我们在命令行执行如下语句:

D:\cgg.exe how are you!

这么执行后,argv是4,而不是3,因为D:\cgg.exe也是一个参数,而它就保存在argv[0]中。

这一点千万不能忘记。

变量相关

✔ 用字符串初始化字符数组

主要是长度的问题,我们知道C中的字符串是以一个空字符作为字符串的结尾标志。所以,我们在赋值时,要注意字符数组的长度要比字符串的长度多1。比如下面这段代码是无法通过编译的:

#include

int main(){

char a[4]="abcd";

return 0;

而我们只需要把a的长度改为5就好了。

✔ 赋初值问题

这是一个老生常谈的问题,但是还是很容易被忽略。所以在这里再说一下。当遇到一些计算求值类的题,一定要注意把变量初始化,赋初值,比如0

✔ 转义字符问题

我们知道C语言中是有转义字符的。转义字符以\开头,所以,我们是不能在一对单引号中间直接放置右斜杠的,还有就是注意,斜杠后如果是十六进制的表示码,开头是x或X,而不是0x或0X

✔ 常用ASCII码记忆

常用的无非是大小写字母和数字,这时一定需要记住的(除非你是上机考试)。

打开网易新闻 查看精彩图片

✔ 字符串常量占用内存问题

我们知道C中的字符串一定是以空字符‘\0’结尾,而这个空字符是不显示的。所以有的出题老师会在这里设坑,比如:"abcde"占的内存大小是多少字节?虽然是五个字符,但是实际上还有一个空字符,总共是6字节。

✔ 字符变量值的问题

我们经常会遇到这样的题,问你某个字符的值是什么。

这时我们需要尤其小心,尤其是选择题。

我们举个例子来说:char a='C'

a的值是什么?

是C吗?

如果你说是,那就错了。

我们只能说a的值是‘C’或者67.

想必你应该能明白我想说什么了。

单引号是不能省去的,这一点尤其重要,缺少了单引号,它就不再是一个字符了。

这一点很容易忽略,尤其是选择题,看到C这个选项,一激动就选上了。那就惨了。

✔ 变量类型的范围计算问题

这里只提醒一点,计算范围的时候不要忘记对于有符号的数,有一位符号位。

✔ 字符串常量拼接问题

在C语言中是允许进行字符串拼接的。

也就是说,如果两对引号包含的字符串放在一起,则会被解释成一个字符串。

比如:

#include

int main(){

char a[15]="hello""world";

char b[15]="hel""lo""world";

printf("%s",b);

return 0;

这两个字符串都是合法的,且和“helloworld”等价。

数组相关

✔ 数组下标越界

这个……恐怕是个程序员都会出过的bug,从你初学编程语言到你退休。

考试也可能会出现这个问题,多半是在for循环里出现。

二维数组中行列计算问题

我们可能会经常看到这样的题,就是另一个指针等于二维数组中的某个地址单元,然后对指针进行加一之类的操作,然后问输出的值。

而我们知道这里会有两种不同的效果,一种是行计算,一种是列计算,二者的含义是,行计算,每次加一,地址会移动一行;而列计算,每次加一,地址只会移动一格。

比如:

#include

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

char a[3][4]={{"you"},{"I"},{"we"}};

char (*p)[4];

p=a;

printf("%s",*(p+1));

return 0;

这里输出的就是第二个字符串I

具体的分类为:

打开网易新闻 查看精彩图片

✔ 字符数组赋值问题

我们知道我们可以这样声明一个字符数组:

char a[12]="helloworld!";

但是,字符数组在被定义之后,是不被允许这样直接赋值的。

比如下面这段代码是不会通过编译的:

char a[12];

a="helloworld!";

结构体相关

✔ 结构体所包含的变量不可初始化

这一点在C++中是被允许的,但是C中是不允许的。

所以习惯了写C++的同学尤其需要注意。

不要忘记了。

✔ 查看结构体内部变量所占内存大小

这里我们只举个例子,比如我们有ABC这个结构体,它有变量x,

则sizeof(((struct ABC*)0)->x)得到的就是x占用的内存大小。

这里的0,只要是int型的量就可以。

库函数相关

✔ 绝对值函数

注意math.h中有两个绝对值函数,一个是abs(),另一个是fabs()

其中前者是处理整型变量的,而后者是处理浮点型变量的

scanf()和printf()的返回值问题

我们通常会把它们当作输入输出语句使用,单独成行,总是自动忽略它们的返回值,但其实它们是有返回值的。

那么它们的返回值是什么呢?

printf的返回值是输出的数据个数,而scanf的返回值则是读入的数据个数。

我们可以来看个例题:

#include

int main(){

int x;

printf("2:%d",printf("1:%d,",scanf("%d",&x)));

return 0;

请问它的输出结果是什么?

答案是

1:1,2:4

为什么是4?不知道你是不是想这么问。

printf中所说的数据个数,其实是把输出的每个字符都看成一个单独的数据,也就是有多少字符加上我们传入的数据,就是它的返回值。

✔ fclose()的返回值问题

和scanf、printf一样,我们经常忽略fclose的返回值,其实他也是有返回值的。

如果成功关闭流,那么返回0,如果未成功关闭则返回-1。

预编译相关

✔ 注意宏定义的替换原则

我们知道宏定义实际上只是字符串的替换,并不是函数一样的东西,所以我们在做题时也需要严格替换,替换完成之前,不要进行任何计算。

打开网易新闻 查看精彩图片

我们举个例子:

答案是多少?54?那就错了。实际上应该是48.

这里我们不能把5+1算出来6再带入,而是直接代入,这样你就会发现,5和1不是直接加在一起的。