《TC++PL》第六章笔记——表达式和语句

1、桌面计算器的例子
这是一个和编译原理有关的例子,因此比较难懂。分析器的设计思路完全遵照给定计算器的“语法”——一种递归下降风格的描述。因此,语

法分析的三个函数——expr()、term()、prim()之间相互递归调用,这也是本程序的一大风格。这个例子很重要,接下来的三章都要用到它来

展示不同的技术。我也在另一篇文章里对这个程序做过一点分析。感觉就是,没有学过编译原理,好吃亏啊。
由于上述三个函数循环调用,故至少有一个函数必须在定义前先声明,而且声明语句还要放在调用它的函数的前面。这个以后会有更详细的讨

论。
我的机器上double类型遇到“除以零”的情况程序并不会崩溃,如前所说,只是把一个“无穷大”赋给了那个商。但程序仍不允许这样,它会

提前检查出来并调用error(),这种错误处理是一种简单高效但缺陷很大的处理方式——在第九章还有讨论。

技巧:程序建立了一些变量映射,允许用户在计算器里添加变量。而使用这个变量的时候,可以用
double& v = table[string_value];
这真是引用的一大妙用。

还有,在get_token()函数里面,return语句用得很有水平,比如:
return curr_tok = Token_value(ch);
函数不仅可以返回当前的token状态,还在同一语句里把这一状态赋给了全局变量curr_tok。这不仅是一种简洁,在一个语句中做两件事对维护

很有帮助。如果分开做,在修改程序时就可能修改了一个而忘了另一个。

全局变量也是函数的一种通讯方式。

错误处理的一种思路:报告错误,然后给程序返回一个不大可能出问题的值。如果是非交互式的使用,记录下错误的“位置”也很重要。

2、命令行参数的问题
调用main()函数的规定是与C共享的,因此命令行参数里用到的是C风格的数组和字符串。
int main( int argc, char* argv[] ){/*...*/}
该程序的名字也作为一个参数传进来,所以argc至少是1。而参数表总是以0结尾,所以argv的类型是char*[argc+1]。要想使命令行读入的字符

串的处理方法和程序中标准输入的处理方法统一起来,就要把这些字符串写道字符串流里面去。从字符串读入的流称为istringstream。可以用

一个全局的istream*去指向这个字符串流或者是cin。

3、用库,用库,用库!
用库不是作弊,除了专门的算法训练以外。用库可以使程序结构更加清晰。

4、运算符及其优先级、结合性
第一,不用死记;第二,搞不清楚加括号
但还是有一些要提出来:*的优先级不如++;按位逻辑运算的优先级不如==;后增量优先级高于前增量。
一元运算符和赋值运算符是右结合的,其余都是左结合。
如果x是左值,那么++x仍然是一个左值。但x++不是。
sizeof、指针相减的类型分别是size_t和ptrdiff_t,都在<cstddef>里面定义。
上溢、下溢和除零都不会抛出标准异常。
当你开始感到需要括号时,你或许就应该考虑通过额外的变量将表达式分开。

5、按位逻辑运算符
应用于整形和枚举。不能应用于浮点类型。用来实现“位向量”的运算。
注:关于左移和右移。根据我对这些语句的反汇编(vc编译器),发现:无论是有符号数还是无符号数,左移均生成逻辑左移指令shl,而右移

时,有符号数生成算数右移指令sar,而无符号数生成shr。

6、增量
前增量是左值,后增量不是。

7、自由存储的问题
首先讨论所谓“命名”对象。命名对象的生存时间由这个名字的作用域决定。如果需要建立生存时间不依赖于建立它的作用域的对象,就需要

进行动态分配。由new分配的对象被说成是在“自由存储里的”(堆对象)。
顺便说一句,delete可以应用于0,不会造成任何影响。
new出来的对象要比“静态的”对象稍大一点,典型情况下,要用一个机器字保存大小。

8、储存耗尽
回抛出一个bad_alloc异常。用set_new_handler可以订制自己的储存耗尽处理函数。

9、显式类型转换
四种cast: static_cast、dynamic_cast、const_cast、reinterpret_cast
其中static_cast有些还具有可移植性,但极少有reinterpret_cast是这样的。这四种转换各有用途,尽量少用,尤其尽量少用

reinterpret_cast,但更不要用C风格的转换。

10、构造函数风格的强制
对于内部类型,T(e)等价于(T)e,这也是不安全的。
指针转换不能直接采用T(e)的形式,但typedef以后就可以。
记法T()可以产生T类型的默认值。对于内部类型也是如此,这就为模板提供了通用性的基础。

11、声明语句
一句声明就是一条语句。声明允许出现在任何写语句的地方,这是为了最大限度地减少由于未初始化的变量而导致的错误。
单赋值风格:对象在初始化之后就不再改变。
将对象的定义推迟到初始式使用之时,还可获得更好的执行性能。

12、选择语句
注意:一,短路求值;二,case语句后面有时候故意不加break,这时候需要加上注释。
可用条件表达式替代(?:)
可以在选择语句里声明变量——在最小作用域里声明变量以避免误用变量。这样的变量作用域从它的声明点一直延伸到这个条件所控制的语句

结束。事实上,vc编译器并没有这样处理,在vc中这个变量的作用于一直延伸到该条语句所在的作用域结束。呵呵,又是实现问题。

13、goto语句与初始式
goto语句,按BS的说法就是“臭名昭著”,极少用到。但如果要从多层嵌套中跳出来,有时候还得用这个(效率高)。
但goto不能跳过初始式,看下面的例子:
void func( int a )
{
   if( a == 1 )
    goto label1;

   int j = 1;
     
label1:
   ++a;
}
编译后给出错误:
error C2362: initialization of 'j' is skipped by 'goto label1'
但把int j嵌套到一个块中,就可以避免编译错误:

void func( int a )
{
   if( a == 1 )
    goto label1;
   {    
   int j = 1;     
   }
label1:++a;
}
呵呵,j是不能在外层作用域使用的。随便了。

14、注释问题
明智地使用注释,一致性地使用缩进编排
糟糕的注释还不如没有
不应再为语言本身已经说明白的东西添加注释
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值