c++学习(三):表达式和语句

四、 表达式
1. 算术运算符
高优先级的操作符要比低优先级的结合得更紧密。这些算术操作符都是左结合。

操作符

功能

用法

+

unary plus(一元正号)

 

-

unary minus(一元负号)

 

*

multiplication(乘法)

 

/

division(除法)

 

%

remainder(求余)

 

+

addition(加法)

 

-

subtraction(减法)

 

 
2. 位操作符
左结合,移位操作符具有中等优先级:其优先级比算术操作符低,但比关系操作符、赋值操作符和条件操作符优先级高。

操作符

功能

 

~

位求反

 

<< 

左移

在右边插入 0 以补充空位

>> 

右移

如果其操作数是无符号数,则从左边开始插入 0;如果操作数是有符号数,则插入符号位的副本或者 0 值

&

位与

如果两个操作数对应的位都为 1,则操作结果中该位为 1

^

位异或

在每个位的位置,如果两个操作数对应的位只有一个为 1,则操作结果中该位为 1,否则为 0。

|

位或

 

 

    1. 与其他二元操作符不同,赋值操作具有右结合特性。多个赋值操作中,各对象必须具有相同的数据类型,或者具有可转换为同一类型的数据类型。
  2. 使用复合赋值操作时,左操作数只计算了一次;而使用相似的长表达式时,该操作数则计算了两次,第一次作为右操作数,而第二次则用做左操作数。
  3. 只有在必要时才使用后置操作符:因为前置操作需要做的工作更少,只需加1后返回加1后的结果即可。而后置操作符则必须先保存操作数原来的值,以便返回未加1之前的值作为操作的结果。
  4. 假设有一个指向类类型对象的指针(或迭代器),下面的表达式相互等价:
    (*p).foo; // dereference p to get an object and fetch its member namedfoo
    p->foo; // equivalent way to fetch the foo from the object to whichp points

3. sizeof
sizeof操作符的作用是返回一个对象或类型名的长度,返回值的类型为size_t,长度的单位是字节。
该操作符有以下三种语法形式:
  sizeof (type name);
  sizeof (expr);
  sizeof expr;
将 sizeof 用于 expr 时,并没有计算表达式 expr 的值。特别是在 sizeof*p 中,指针 p 可以持有一个无效地址,因为不需要对p做解引用操作。
使用 sizeof 的结果部分地依赖所涉及的类型:
  • 对 char 类型或值为 char 类型的表达式做 sizeof 操作保证得 1。
  • 对引用类型做 sizeof 操作将返回存放此引用类型对象所需的内在空间大小。
  • 对指针做 sizeof 操作将返回存放指针所需的内在大小;注意,如果要获取该指针所指向对象的大小,则必须对指针进行引用。
  • 对数组做 sizeof 操作等效于将对其元素类型做 sizeof 操作的结果乘上数组元素的个数。

  5. 逗号表达式是一组由逗号分隔的表达式,这些表达式从左向右计算。逗号表达式的结果是其最右边表达式的值。如果最右边的操作数是左值,则逗号表达式的值也是左值。
4. 操作符的优先级

操作符

结合性

功能

用法

::

L

全局作用域

:: name

::

L

类作用域

class::name

::

L

命名空间作用域

namespace::name

.

L

成员选择

  • object.member

->

L

成员选择

pointer.member

[]

L

下标

variable[expr]

()

L

函数调用

name(expr_list)

()

L

类型构造

type(expr_list)

++

R

后自增

 

--

R

后自减

 

typeid

R

类型ID

typeid (type/expr)

explicit cast

R

显示类型转换

cast_name<type>(expr)

sizeof

R

 

sizeof expr/(type)

++

R

前自增

 

--

R

前自减

 

~

R

位求反

 

R

逻辑非

 

-

R

一元负号

 

+

R

一元正号

 

*

R

解引用

 

&

R

取地址

 

()

R

类型转换

(type) expr

new

R

创建对象

 

delete

R

释放对象

 

delete[]

R

释放数组

 

->*

L

指向成员操作的指针

ptr ->* ptr_to_member

.*

L

指向成员操作的指针

  • obj .*ptr_to_member

*

L

 

 

/

L

 

 

%

L

 

 

+

L

加法

 

-

L

减法

 

<< 

L

位左移

 

>> 

L

位右移

 

L

 

 

<=

L

 

 

L

 

 

>=

L

 

 

==

L

 

 

!=

L

 

 

&

L

位与

 

^

L

位异或

 

|

L

位或

 

&&

L

 

 

||

L

 

 

?:

R

 

 

=

R

 

 

*=,/=,%=,+=,-=,<<=,>>=,

&=,|=,^=

R

复合赋值操作

 

throw

R

 

 

,

L

 

 


5. 求值顺序
&& 和 || 操作符计算其操作数的次序:当且仅当其右操作数确实影响了整个表达式的值时,才计算这两个操作符的右操作数。C++中,规定了操作数计算顺序的操作符还有条件(?:)和逗号操作符。除此之外,其他操作符并未指定其操作数的求值顺序。
6. new 与 delete
定义变量时,必须指定其数据类型和名字。而动态创建对象时,只需指定其数据类型,而不必为该对象命名。取而代之的是,new表达式返回指向新创建对象的指针,我们通过该指针来访问此对象。

int i; // named, uninitialized int variable
int *pi = new int; // pi points to dynamically allocated,unnamed, uninitialized int

如果不提供显式初始化,动态创建的对象与在函数内定义的变量初始化方式相同。对于类类型的对象,用该类的默认构造函数初始化;而内置类型的对象则无初始化。

int *pi = new int(1024);     // object to which pi points is 1024
string *ps = new string(10, '9'); // *ps is "9999999999"
string *ps = new string; // initialized to empty string
int *pi = new int; // pi points to an uninitialized int
string *ps = new string(); // initialized to empty string
int *pi = new int(); // pi points to an int value-initialized to 0

C++ 提供了 delete 表达式释放指针所指向的地址空间。
delete pi;该命令释放pi指向的int型对象所占用的内存空间。
如果指针指向不是用new分配的内存地址,则在该指针上使用delete是不合法的。C++ 保证:删除 0 值的指针是安全的。删除指针后,该指针变成悬垂指针。悬垂指针指向曾经存放对象的内存,但该对象已经不再存在了。悬垂指针往往导致程序错误,而且很难检测出来。一旦删除了指针所指向的对象,立即将指针置为0,这样就非常清楚地表明指针不再指向任何对象。

下面三种常见的程序错误都与动态内存分配相关:
  1. 删除(delete)指向动态分配内存的指针失败,因而无法将该块内存返还给自由存储区。删除动态分配内存失败称为“内存泄漏(memory leak)”。内存泄漏很难发现,一般需等应用程序运行了一段时间后,耗尽了所有内存空间时,内存泄漏才会显露出来。
  2. 读写已删除的对象。如果删除指针所指向的对象之后,将指针置为 0 值,则比较容易检测出这类错误。
  3. 对同一个内存空间使用两次 delete 表达式。当两个指针指向同一个动态创建的对象,删除时就会发生错误。如果在其中一个指针上做 delete 运算,将该对象的内存空间返还给自由存储区,然后接着 delete 第二个指针,此时则自由存储区可能会被破坏。
7. 类型转换
  1. 隐式类型转换
  在混合类型的表达式中
  用作条件的表达式被转换为 bool 类型
  用一表达式初始化某个变量,或将一表达式赋值给某个变量
  整型提升:对于所有比 int 小的整型,包括 char、signed char、unsigned char、short 和 unsigned short,如果该类型的所有可能的值都能包容在int内,它们就会被提升为int型,否则,它们将被提升为unsigned int。如果将bool值提升为int,则false转换为0,而true则转换为 1。
  对于包含 signed 和 unsigned int 型的表达式,表达式中的 signed 型数值会被转换为 unsigned 型。

  指针转换:在使用数组时,大多数情况下数组都会自动转换为指向第一个元素的指针。不将数组转换为指针的例外情况有:数组用作取地址(&)操作符的操作数或 sizeof 操作符的操作数时,或用数组对数组的引用进行初始化时,不会将数组转换为指针。C++ 还提供了另外两种指针转换:指向任意数据类型的指针都可转换为void* 类型;整型数值常量 0 可转换为任意指针类型。

  2. 显示转换:static_cast、dynamic_cast、const_cast 和 reinterpret_cast
  强制类型转换符号的一般形式如下:cast-name<type>(expression);
    dynamic_cast:支持运行时识别指针或引用所指向的对象。
    const_cast :将转换掉表达式的 const 性质。
    static_cast:编译器隐式执行的任何类型转换都可以由 static_cast 显式完成。
    reinterpret_cast:通常为操作数的位模式提供较低层次的重新解释。

五、 语句
1. 常用语句注意点
复合语句:通常被称为块,是用一对花括号括起来的语句序列。块标识了一个作用域,在块中引入的名字只能在该块内部或嵌套在块中的子块里访问。通常,一个名字只从其定义处到该块的结尾这段范围内可见。

switch语句中default后必须要有一个语句,case后必须是整型常量表达式。对于 switch 结构,只能在它的最后一个 case 标号或 default 标号后面定义变量。

While语句:在循环条件中定义的变量在每次循环里都要经历创建和撤销的过程。

可以在 for 语句的 init-statement 中定义多个对象;但是不管怎么样,该处只能出现一个语句,因此所有的对象必须具有相同的一般类型。

与 while 语句不同。do-while 语句总是以分号结束。

int a[]={1,2,3,4,5};
int *source=a;
size_t sz=sizeof(a)/sizeof(*a);
int *dest=new int[sz];
while(source!=a+sz){
  *dest++=*source++;
}
int *p=dest-1;
while(p!=dest-sz-1){
  std::cout<<*p<<std::endl;
  --p;
}

goto 语句不能跨越变量的定义语句向前跳转。

2. 异常处理
throw 语句使用一个表达式,该表达式是 runtime_error 类型的对象。runtime_error类型是标准库异常类中的一种,在 stdexcept 头文件中定义。
每一个标准库异常类都定义了名为 what 的成员函数。这个函数不需要参数,返回const char* 类型值,它返回的指针指向一个 C 风格字符串。
如果不存在处理该异常的 catch 子句,程序的运行就要跳转到名为terminate 的标准库函数,该函数在 exception 头文件中定义。这个标准库函数的行为依赖于系统,通常情况下,它的执行将导致程序非正常退出。
标准库异常类定义在四个头文件中:
  1. exception 头文件定义了最常见的异常类,它的类名是 exception。这个类只通知异常的产生,但不会提供更多的信息。
  2. stdexcept 头文件定义了几种常见的异常类
exception 最常见的问题,只定义了默认构造函数
runtime_error 运行时错误:仅在运行时才能检测到问题
range_error 运行时错误:生成的结果超出了有意义的值域范围
overflow_error 运行时错误:计算上溢
underflow_error 运行时错误:计算下溢
logic_error 逻辑错误:可在运行前检测到问题
domain_error 逻辑错误:参数的结果值不存在
invalid_argument 逻辑错误:不合适的参数
length_error 逻辑错误:试图生成一个超出该类型最大长度的对象
out_of_range 逻辑错误:使用一个超出有效范围的值
3. new 头文件定义了 bad_alloc 异常类型,提供因无法分配内在而由 new抛出的异常,只定义了默认构造函数
4. type_info 头文件定义了 bad_cast 异常类型,只定义了默认构造函数
#define NDEBUG
预处理器还定义了其余四种在调试时非常有用的常量:
__FILE__ 文件名
__LINE__ 当前行号
__TIME__ 文件被编译的时间
__DATE__ 文件被编译的日期
另一个常见的调试技术是使用 NDEBUG 预处理变量以及 assert 预处理宏。assert 宏是在 cassert 头文件中定义的,所有使用 assert 的文件都必须包含这个头文件。预处理宏有点像函数调用。assert 宏需要一个表达式作为它的条件:
assert(expr)
只要 NDEBUG 未定义,assert 宏就求解条件表达式 expr,如果结果为false,assert 输出信息并且终止程序的执行。如果该表达式有一个非零值,则 assert 不做任何操作。

posted @ 2012-10-16 10:42  淩風  阅读(1452)  评论(0编辑  收藏  举报