表达式

来源:互联网 发布:js模块化 剥离模块 编辑:程序博客网 时间:2024/03/28 21:46

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

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

操作数:该操作符执行什么操作以及操作结果的类型——取决于操作数的类型。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.newdelete表达式

  定义变量时,必须指定其数据类型和名字。而动态创建对象时,只需指定其数据类型,而不必为该对象命名。取而代之的是,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第二个指针则自由存储区可能会被破坏。

0 0
原创粉丝点击