您的位置:首页 > 其它

表达式

2016-02-16 20:57 197 查看
在前面文章中,多次提到表达式,他大量的出现在程序中,这篇文章将着重分析表达式。表达式由一个或多个操作数通过操作符组合而成。最简单的表达式仅包含一个字面值常量或者变量。较复杂的表达式则由操作符以及一个或多个操作数构成。

每个表达式都会产生一个结果。如果表达式中没有操作符,则其结果就是操作数本身(例如,字面值常量或变量)的值。

操作数:该操作符执行什么操作以及操作结果的类型——取决于操作数的类型。C++提供了一元操作符,二元操作符两种操作符和三元操作符。作用在一个操作数上的操作符称为一元操作符,如取地址操作符(&)和解引用操作符(*);而二元操作符则作用于两个操作数上,如加法操作符(+)和减法操作符(-)。我们将在下面一一介绍。

1.算术操作符

下表按优先级来对操作符进行分组:



正负一元优先级最高,然后是乘除,最后是加减。这些算术操作符都是左结合。这就意味着当操作符的优先级相同时,这些操作符从左向右依次与操作数结合。

2.关系操作符和逻辑操作符



以上操作符返回的都是bool值。

3.位操作符

位操作符使用整型的操作数。位操作符将其整型操作数视为二进制位的集合,为每一位提供检验和设置的功能。另外,这类操作符还可用于bitset 类型(后面将会讲到)的操作数,该类型具有这里所描述的整型操作数的行为。

对于位操作符,由于系统不能确保如何处理其操作数的符号位,所以强烈建议使用unsigned 整型操作数。



上表中我主要就谈下<<和>>,其他的操作符过于简单我就不说了(翻翻基本课本就OK了)。

<< 和 >> 操作符提供移位操作,其右操作数标志要移动的位数。这两种操作符将其左操作数的各个位向左(<<)或向右(>>)移动若干个位(移动的位数由其右操作数指定),从而产生新的值,并丢弃移出去的位。那么移位后旧的移出去了,用什么来补空位。左移操作符(<<)在右边插入 0 以补充空位。对于右移操作符(>>),如果其操作数是无符号数,则从左边开始插入 0;如果操作数是有符号数,则插入符号位的副本或者 0 值。移位操作的右操作数不可以是负数,而且必须是严格小于左操作数位数的值。否则,操作的效果未定义。

一见到<<和>>是不是感到以前用到过,没错输入输出是我们经常会用到<<和>>,输入输出标准库(又名IO标准库)重载了位操作符>>和<<。重载的位操作符和内置类型版本具有相同的优先级和结合性。与其他二元操作符一样,它也是左结合的。

4.赋值操作符(=)

赋值表达式的值是其左操作数的值,其结果类型为左操作数的类型。与下标和解引用操作符一样,复制操作也返回左值。但与其他二元操作符不同(前面的>>和<<就是左结合),他是右结合的。



C++ 语言不仅对加法,而且还对其他算术操作符和位操作符提供了这种用法,称为复合赋值操作。复合赋值操作符的一般语法格式为:



其中,op= 可以是下列十个操作符之一:



5.自增和自减操作符(++,---)

如何使用我就不细细讲了,大家可以看看基本书籍,这里我主要强调一个习惯:只有在必要时才使用后置操作符。如果没有特殊要求,尽量使用前自增操作。

原因:因为前置操作需要做的工作更少,只需加1后返回加 1 后的结果即可。而后置操作符则必须先保存操作数原来的值,以便返回未加 1 之前的值作为操作的结果。对于 int 型对象和指针,编译器可优化掉这项额外工作。但是对于更多的复杂迭代器类型,这种额外工作可能会花费更大的代价。因此,养成使用前置操作这个好习惯,就不必操心性能差异的问题。

6.结合性与优先级

结合性

操作符

功能

用法

左结合

左结合

左结合

::

::

::

全局作用域

类作用域

名字空间作用域

::name

class::name

namespace::name

左结合

左结合

左结合

左结合

左结合

.

->

[]

()

()

成员选择

成员选择

下标

函数调用

类型构造

object.member

point->member

variable[index]

function(args)

class(args)

右结合

右结合

右结合

右结合

右结合

++

--

typeid

typeid

显示强制类型转换

后自增操作

后自减操作

类型ID

运行时ID

类型转换

value++

vValue--

typeid(type)

typeid(expr)

cast_name<type>(args)

右结合

右结合

右结合

右结合

右结合

右结合

右结合

右结合

右结合

右结合

右结合

右结合

右结合

右结合

sizeof

sizeof

++

--

~

!

-

+

*

&

()

new

delete

delete()

对象的大小

类型的大小

前自增

前自减

位求反

逻辑非

一元负号

一元正号

解引用

取地址

类型转换

创造对象

释放对象

释放数组

sizeof expr

sizeof(type)

++value

--value

~expr

!expr

-expr

+expr

*expr

&expr

(type)expr

new type

delete expr

delete []expr

左结合

左结合

->*

.*

指向成员操作的指针

指向成员操作的指针

ptr ->* ptr_to_member

obj .*ptr_to_member

左结合

左结合

左结合

*

/

%

乘法

除法

求模

expr*expr

expr/expr

expr%expr

左结合

左结合

+

-

加法

减法

expr+expr

expr-expr

左结合

左结合

<<

>>

位左移

位右移

expr<<expr

expr>>expr

左结合

左结合

左结合

左结合

<

<=

>

>=

小于

小于等于

大于

大于等于

expr<expr

expr<=expr

expr>expr

expr>=expr

左结合

左结合

==

!=

相等

不相等

expr==expr

expr!=expr

左结合

&

位与

expr&expr

左结合

^

位与或

expr^expr

左结合

|

位或

expr|expr

左结合

&&

逻辑与

expr&&expr

左结合

||

逻辑或

expr||expr

右结合

?:

条件操作

expr?:expr:expr

右结合

=

*=,/=.%=,+=,-=,<<=,>>=,&=,|=,^=

赋值操作

复合赋值操作

value = expr

valu += expr

右结合

throw

抛出异常

throw expr

左结合



逗号

expr,expr

上述表格建议新手自己重新打一遍,手打一遍基本上就都记住了

编程好习惯提示:

《C++Primer》认为一个好的程序员书写的代码应该简练,比如:



他们习惯上述写法,而不会像下面:



但是在这里,我想强调一下,我们还需要考虑可读性的问题,现在代码规模都是需要多人完成,我们不仅需要考虑代码的简练,还需要考虑代码的可读性。

最好的解决方法就是,要不然加上圆括号,来明确顺序,要不然可以书写注释



7.new和delete表达式

定义变量时,必须指定其数据类型和名字。而动态创建对象时,只需指定其数据类型,而不必为该对象命名。取而代之的是,new 表达式返回指向新创建对象的指针,我们通过该指针来访问此对象:



上图虽然给指针初始化了,但是pi指向的int整型并没有初始化。

7.1动态创建对象的初始化



7.2 动态创建对象的初始化

如果不提供显式初始化,则和在函数中定义变量的初始化一样,对于类类型的对象,用该类的默认构造函数,内置类型的对象则无初始化。在动态创建对象时,我们一样需要养成对他进行初始化的好习惯。

对于内置类型或没有定义默认构造函数的类型,采用不同初始化方式则有显著的差别,如下图:



注意:值初始化的 () 语法必须置于类型名后面,而不是变量后,如下图:



7.3 创建动态数组

7.3.1 动态数组定义

数组变量通过指定类型、数组名和维数来定义。而动态分配数组时,只需指定类型和数组长度,不必为数组对象命名:



new 表达式需要指定指针类型以及在方括号中给出的数组维数,该维数可以是任意的复杂表达式。创建数组后,new 将返回指向数组第一个元素的指针。在自由存储区中创建的数组对象是没有名字的,程序员只能通过其地址间接地访问堆中的对象。

7.3.2 初始化动态分配的数组

动态分配数组时,如果数组元素具有类类型,将使用该类的默认构造函数实现初始化;如果数组元素是内置类型,则无初始化。

也可使用跟在数组长度后面的一对空圆括号,对数组元素做值初始化



注意:对于动态分配的数组,其元素只能初始化为元素类型的默认值,而不能像数组变量一样,用初始化列表为数组元素提供各不相同的初值。

7.3.3 动态空间的释放



在关键字 delete 和指针之间的空方括号对是必不可少的:它告诉编译器该指针指向的是自由存储区中的数组,而并非单个对象。如果遗漏了空方括号对,这是一个编译器无法发现的错误,将导致程序在运行时出错。

理论上,回收数组时缺少空方括号对,至少会导致运行时少释放了内存空间,从而产生内存泄漏。对于某些系统和/或元素类型,有可能会带来更严重的运行时错误。因此,在释放动态数组时千万别忘了方括号对。

7.4 耗尽内存

尽管现代机器的内存容量越来越大,但是自由存储区总有可能被耗尽。如果程序用完了所有可用的内存,new 表达式就有可能失败。如果 new 表达式无法获取需要的内存空间,系统将抛出名为 bad_alloc 的异常。

7.5 撤销动态创建的对象

动态创建的对象用完后,程序员必须显式地将该对象占用的内存返回给自由存储区。C++ 提供了 delete 表达式释放指针所指向的地址空间

注意:如果指针指向不是用 new 分配的内存地址,则在该指针上使用delete 是不合法的。

C++ 没有明确定义如何释放指向不是用 new 分配的内存地址的指针。



编程好习惯提示:

一旦删除了指针所指向的对象,立即将指针置为0,这样就非常清楚地表明指针不再指向任何对象。因为删除指针后,该指针变成悬垂指针。悬垂指针指向曾经存放对象的内存,但该对象已经不再存在了。悬垂指针往往导致程序错误,而且很难检测出来。

注意;动态内存的管理容易出错

1)删除指向动态分配的内存失败,导致无法将该块返回给系统自由存储区,这种情况被称为“内存泄露”。这种情况大家至少都听过,且他不好避免而且不容易被发现,只有耗尽所有内存时,才会表现出来

2)读写已经删除了的对象如果删除指针所指向的对象之后,将指针置为 0 值,则比较容易检测出这类错误。

3)对同一个内存空间使用两次 delete 表达式。一个对象有个2个指针指向。第一次使用delete释放一个指针将返回对象的内存空间,第二次使用delete第二个指针则自由存储区可能会被破坏。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: