• Home
  • About
    • Road to Coding photo

      Road to Coding

      只要那一抹笑容尚存, 我便心无旁骛

    • Learn More
    • Email
    • Github
  • Posts
    • All Posts
    • All Tags

语法陷阱与词法陷阱

21 Jan 2018

最近重装了系统,/挂载在固态里面是真的快,但是,KDE的中文字体现在搞得我想吃shi,楷体是真的难受

(upate:适应了几天,还不错,谁的Linux有我的楷体sao(滑稽));)

不扯了,不扯了,C陷阱与缺陷是很好的一本书,这一篇是词法与语法中的陷阱

其中有几个是很多人整天苦恼地问题呢

词法陷阱

1. = 不同于 ==

这个问题,基本上只要是使用过C语言的都是知道的,赋值运算符与比较运算符是不能混淆的

尤其是在,选择或者循环控制语句中,混淆使用会导致不可预料的后果

但是,平时应该如何规避这样的错误呢?

显式的使用括号进行判别

一般情况下,在if while等控制语句中需要的是 == 运算符,但是遇到必要的使用 = 进行判断的时候呢?

/*检查新值是否为0*/

if (a = b)
	foo( );

可以写作:

if ((a = b) != 0)    //显式的表示更加明确
	foo( );

2. & 和 | 不同于 && 和 ||

在之前看C的书籍与CS:APP都提到了这个问题

只有在限定数值范围的情况下,才能得到相同的结果

3. 词法分析中的”贪心法”

其实只要了解过,正则表达式的人,就可以很方便的理解贪心法

即是指:进行词法分析时,尽可能多的进行运算符的匹配

比如:

a---b #=> a -- - b

y = x/*p #=> y = x /* p                            */

二义性,应该这样写:

y = x/(*p)

而且,在ASCI标准没有出现之前,会有许许多多尴尬的情况

准确得讲,因为其中不标准的规范,才会有上古时期很难阅读的代码

而在现在,基本上都支持ASCI标准,所以,好用多了

4. 整型常量

众所周知,在C中支持,二进制,八进制,十进制,十六进制,采用不同的书写方式

所以,会出现下面这样的情况,将十进制误写成其他进制

struct {
	int part_number;
	char *description;
}parttab[] = {
	046, "left-handed weight",
	047, "right-handed weight",
	125, "frammis"
};

上面便出现了将十进制误写成八进制的情况,需要严加防范

5. 字符与字符串

…

语法陷阱

1. 理解函数声明

函数声明,是C中进行程序设计所必不可少的,

有难度吗?

**有,当你遇到,函数指针,返回值为指针的函数,嵌套时,有你好受的 ! **

来看一个例子,这是选自 < C陷阱与缺陷 > 的例子

(*(void(*)( ))0)( );

上面这个东西是真的看的人头疼!

那么,我们拆开来看吧!

首先,是这一部分,

(*(...)0)( );

可以清楚的看到,

如果没有后面的0,那么便是进行函数指针的调用,比如 (*fp)( );

但是,加上0,就不能解释为简单地函数指针调用了,而是进行强制类型转换!

即,此处是将0,转换成为一个函数指针,这样才能实现需要:调用0位置的子例程

再来看,括号内的内容

(void(*)( ))

这里就要介绍一种技巧了,即是获取到类型转换符的方法

很简单:将标识符,分号,去掉之后.用括号封装剩下的内容即可

来举个例子:

void (*fp)(ARGV);      /*其中括号是必要的,因为优先级*/

上面的例子,便是一个返回类型为void的函数指针,函数指针标识符为fp,

那么,0便是要接受这样的类型转换,将上面的式子转换为类型转换运算符即为

(void(*)())

这样便可以作为0的类型转换符来使用了

表示含义为”返回值为void类型的函数指针的强制类型转换”

现在回过头来,我们再整体分析一遍

(*(void(*)( ))0)( );

(*(强制类型转换为指针,返回值为void)调用地址0处)(函数参数);

上面因为直接进行对0地址处的进程调用,所以写出来的是,强制类型转换为指针类型,

同时也是因为,此处要操作的是函数指针的缘故.

这样的形式是真的复杂,所以我们可以使用typedef来进行别名命名

typedef void(*fp)( );

(*(fp)0)( );

同时,也让我更深刻的理解了,typedef的内容,不仅仅命名结构体,还可以命名函数指针

上面是一个简单地例子,下面我们来看一个更, , ,的例子,库函数中,signal函数的实现

首先来说说需求: 传参为int型的信号值,另一个参数,值用户制定的函数指针

首先,我们来设计一个基础的版本

void signalfunc(int );

很简单对吧,就是一个返回值为空,传参为int值即可

我们现在需要完成一个需求,声明一个指向signalfunc的函数指针

void (*sfp)(int);

没错对吧,sfp即为指向signalfunc的函数指针

所以,我们的signal函数则可以这样写:

void (*signal(ARGV))(int);

其中ARGV是signal的参数,那么signal函数的参数是什么呢?

一个int型的信号值,一个用户自定义的函数

那么,这就简单了,ARGV可以展开写成

void (*signal(int ,void (*)(int)))(int);

所以,现在来看,我们可以这样理解:

使用函数指针,是为了进行简单的函数调用设计,的那是因为C语言特性

一直以来使得,C函数原型(过去叫函数声明)理解晦涩,实质上,可以使用递归德斯向来理解(Pangda)

即,将多层函数指针嵌套的函数原型,进行按层拆封,由内向外,一层一层的进行划分

其中比较麻烦的就是,函数的参都不一致,所以看起来难以辨别,这种时候使用typedef就清晰明了了

比如:


typedef void (*HANDLER)(int);

void (*signal(int, void (*)(int)))(int);

#=> HANDLER signal(int, HANDLER);
void *(*k(int p, int(*l)(int, void(*m)(int))))(int o);

上面这样的形式,一般是比较迷惑人的,

仔细划分,函数名就是k,就是第一层,其他全部是调用的函数指针的形式参数名

这一部分,十分重要,可以让你以后设计出,厉(ri)害(gou)的函数

2. 运算符的优先级

运算符 结合型
( ) [ ] -> . 自左向右
! ~ ++ – - (type) * & sizeof 自右至左
* / % 自左向右
+ - 自左向右
« » 自左向右
< <= => > 自左向右
== != 自左向右
& 自左向右
^ 自左向右
| 自左向右
&& 自左向右
|| 自左向右
?: 自右向左
assignments 自右向左
, 自左向右

上面的便是,运算符优先级表,

反正我觉得不会好记,一般有两种办法处理优先级问题

1. 加括号, 2. 背表

3. 注意语句结束标志的分号

C中使用” ; “作为语句的结尾,让人又爱又恨

好的地方在于:清晰的标志了语句地结束

不好的地方在于: 用错的时候,会出现爆炸般的错误

来看下面这几个例子:

if (a > b);
	func( );

等价:

if (a > b) { }
	func( );

相信很多人都出过这样的错,提前结束判断/循环语句,导致程序出现不可控的错误

当然,有时候,有必要的话应该这样写

if (a > b)
	;
func( );

写空语句的形式,更加清晰明了,

想要以格式来进行程序的逻辑控制,在C中是痴心妄想,游标卡尺倒是还可以

多分号,很危险.同理,少了分号也不得了,来看下面的例子

if (a > b)
	return 
a.one = 1;
a.two = 2;
a.three = 3;

等价:

if (a > b)
	return a.one = 1;
a.two = 2;
a.three = 3;
struct test {
	int a;
	float b;
	struct test *next;
}

main(void)
{
	...
}

则指定了main函数的返回值为test结构体,在一定的情况下,会出现爆炸现象的,(此处不会使用缺省类型int)

4. switch语句

关于switch语句,需要注意的点在于:

关于逻辑控制的break语句

切记,没有break,则会顺序执行

小组面试也考过的

当然没有绝对的错

比如在写一个运算器的时候:

减法也可以转换成为加法

switch (operation) {
	case '-':operation = -operation;
	//此处没有break;
	case '+':....
	....
}

这种时候加上注释,效果更好呦

5. 函数调用与else

其实就是意思一下,大家都懂得

C中函数调用,必须有括号,不然只是计算函数的地址,并不调用,一般linter都会报错

**else,与最紧密相联的if构成一对(在括号不清晰时),还是那句话,不要妄图在C中间加入py那种玩艺(滑稽 **

这本书,怎么说,还是因为放假回来,一开始看不进入其他的书

不过,怎么说,这本书,还挺好看的,(虽然买的是盗版,不过也是钱啊0)

下一章开始就好玩了

另外,我也想像瑞神一样,分析一波C标准库,起码看完,C自身的特性就都懂了

像Pangda那种OOP的C,我还是不想玩了,dan teng,不如直接rb

January 23, 2018 11:41 AM



CTrapsAndPitfalls Share Tweet +1