C++Primer笔记 第五章 表达式

来源:互联网 发布:漫步者煲音箱软件 编辑:程序博客网 时间:2024/05/23 11:20
C++ 支持操作符重载,允许程序员自定义用于类类型时操作符的含义。
标准库正是使用这种功能定义用于库类型的操作符。

表达式由一个或多个操作数通过操作符组合而成。
最简单的表达式仅包含一个字面值常量或变量。
较复杂的表达式则由操作符以及一个或多个操作数构成。

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


5.1. 算术操作符
表 5.1. 算术操作符
操作符      功能                用法
+      unary plus(一元正号)   + expr
-      unary minus(一元负号)  - expr
*      multiplication(乘法)   expr * expr
/      division(除法)         expr / expr
%      remainder(求余)        expr % expr
+      addition(加法)         expr + expr
-      subtraction(减法)      expr - expr


5.2. 关系操作符和逻辑操作符
表 5.2. 关系操作符和逻辑操作符
下列操作符都产生 bool 值
操作符             功能                         用法
!             logical NOT(逻辑非)             !expr
<             less than(小于)                 expr < expr
<=            less than or equal(小于等于)    expr <= expr
>             greater than(大于)              expr > expr
>=            greater than or equal(大于等于) expr >= expr
==            equality(相等)                  expr == expr
!=            inequality(不等)                expr != expr
&&            logical AND(逻辑与)             expr && expr
||            logical OR(逻辑或)              expr || expr


5.3. 位操作符

表 5.3. 位操作符
操作符       功能                   用法
~        bitwise NOT(位求反)       ~expr
<<       left shift(左移)          expr1 << expr2
>>       right shift(右移)         expr1 >> expr2
&        bitwise AND(位与)         expr1 & expr2
^        bitwise XOR(位异或)       expr1 ^ expr2
|        bitwise OR(位或)          expr1 | expr2

5.3.1. bitset 对象或整型值的使用
bitset 类比整型值上的低级位操作更容易使用;

bitset_quiz1.set(27); // indicate student number 27 passed
int_quiz1 |= 1UL<<27; // indicate student number 27 passed

5.3.2. 将移位操作符用于IO
像其他二元操作符一样,移位操作符也是左结合的。
这类操作符从左向右地结合,正好说明了程序员为什么可以把多个输入或输出操作连接为单个语句:


cout << "hi" << " there" << endl;
执行为:
( (cout << "hi") << " there" ) << endl;
在这个语句中,
操作数"hi"与第一个 << 符号结合,
其计算结果与第二个 <<符号结合,
第二个 << 符号操作后,
其结果再与第三个 << 符号结合。

移位操作符具有中等优先级:
其优先级比算术操作符低,但比关系操作符、赋值操作符和条件操作符优先级高。
若 IO 表达式的操作数包含了比IO 操作符优先级低的操作符,相关的优先级别将影响书写该表达式的方式。
通常需使用圆括号强制先实现右结合

5.4. 赋值操作符
赋值操作符的左操作数必须是非 const 的左值;

5.4.1. 赋值操作的右结合性
与下标和解引用操作符一样,赋值操作也返回左值。
同理,只要被赋值的每个操作数都具有相同的通用类型,C++语言允许将这多个赋值操作写在一个表达式中:

int ival, jval;
ival = jval = 0;        // ok: each assigned 0
与其他二元操作符不同,赋值操作具有右结合特性。
当表达式含有多个赋值操作符时,从右向左结合。
上述表达式,将右边赋值操作的结果(也就是 jval)赋给 ival。


多个赋值操作中,各对象必须具有相同的数据类型,或者具有可转换)为同一类型的数据类型:
int ival; int *pval;
ival = pval = 0; // error: cannot assign the value of a pointer to
an int
string s1, s2;
s1 = s2 = "OK"; // ok: "OK" converted to string

5.4.2. 赋值操作具有低优先级

5.4.3. 复合赋值操作符
复合赋值操作符的一般语法格式为:
a op= b;
其中,op= 可以是下列十个操作符之一:
+= -= *= /= %=               // arithmetic operators
<<= >>= &= ^= |=             // bitwise operators

5.5. 自增和自减操作符
自增(++)和自减(--)操作符为对象加1 或减1 操作提供了方便简短的实现方式。
它们有前置和后置两种使用形式;


建议:只有在必要时才使用后置操作符
有使用 C 语言背景的读者可能会觉得奇怪,为什么要在程序中使用前自增操作。
道理很简单:因为前置操作需要做的工作更少,只需加 1 后返回加 1 后的结果即可。
而后置操作符则必须先保存操作数原来的值,以便返回未加 1 之前的值作为操作的结果。
对于 int 型对象和指针,编译器可优化掉这项额外工作。
但是对于更多的复杂迭代器类型,这种额外工作可能会花费更大的代价。
因此,养成使用前置操作这个好习惯,就不必操心性能差异的问题。


5.6. 箭头操作符
C++ 语言为包含点操作符和解引用操作符的表达式提供了一个同义词:
箭头操作符(->)。
点操作符用于获取类类型对象的成员:
item1.same_isbn(item2);                 // run the same_isbn member of item1

5.7. 条件操作符
条件操作符是 C++ 中唯一的三元操作符,它允许将简单的 if-else 判断语句嵌入表达式中。
条件操作符的语法格式为:
cond ? expr1 : expr2;

5.8. sizeof 操作符
sizeof 操作符的作用是返回一个对象或类型名的长度,
返回值的类型为size_t,长度的单位是字节。
size_t 表达式的结果是编译时常量,

该操作符有以下三种语法形式:
sizeof (type name);
sizeof (expr);
sizeof expr;

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


此类表达式通常用于for 循环:
int cnt = ivec.size();

// add elements from size... 1 to ivec
for(vector<int>::size_type ix = 0;ix != ivec.size(); ++ix, --cnt)
  ivec[ix] = cnt;

5.10. 复合表达式的求值
含有两个或更多操作符的表达式称为复合表达式。
在复合表达式中,操作数和操作符的结合方式决定了整个表达式的值。
表达式的结果会因为操作符和操作数的分组结合方式的不同而不同。
操作数的分组结合方式取决于操作符的优先级和结合性。

5.10.1. 优先级
表达式的值取决于其子表达式如何分组;
圆括号凌驾于优先级之上;


5.10.2. 结合性
结合性规定了具有相同优先级的操作符如何分组。


表 5.4. 操作符的优先级






5.10.3. 求值顺序
在第 5.2 节中,我们讨论了 && 和 || 操作符计算其操作数的次序:
当且仅当其右操作数确实影响了整个表达式的值时,才计算这两个操作符的右操作数


5.11. new 和 delete 表达式
使用 new 和 delete 表达式动态创建和释放数组,这两种表达式也可用于动态创建和释放单个对象。
定义变量时,必须指定其数据类型和名字。
而动态创建对象时,只需指定其数据类型,而不必为该对象命名。
取而代之的是,new 表达式返回指向新创建对象的指针,我们通过该指针来访问此对象:


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


这个 new 表达式在自由存储区中分配创建了一个整型对象,
并返回此对象的地址,并用该地址初始化指针 pi。


1. 动态创建对象的初始化
动态创建的对象可用初始化变量的方式实现初始化:

int i(1024);                        // value of i is 1024
int *pi = new int(1024);            // object to which pi points is 1024
string s(10, '9');                  // value of s is "9999999999"
string *ps = new string(10, '9');   // *ps is "9999999999"

C++ 使用直接初始化(direct-initialization)语法规则初始化动态创建的对象。
如果提供了初值,new 表达式分配到所需要的内存后,用给定的初值初始化该内存空间。
在本例中,pi 所指向的新创建对象将被初始化为 1024,而 ps 所指向的对象则初始化为十个9 的字符串。


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

string *ps = new string; // initialized to empty string
int *pi = new int; // pi points to an uninitialized int

通常,除了对其赋值之外,对未初始化的对象所关联的值的任何使用都是没有定义的。


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

delete pi;

该命令释放 pi 指向的 int 型对象所占用的内存空间。
如果指针指向不是用 new 分配的内存地址,则在该指针上使用delete 是不合法的。


4. 零值指针的删除
如果指针的值为 0,则在其上做 delete 操作是合法的,但这样做没有任何意义:
int *ip = 0;
delete ip;        // ok: always ok to delete a pointer that is equal to 0

C++ 保证:删除 0 值的指针是安全的。

5.12. 类型转换
表达式是否合法取决于操作数的类型,而且合法的表达式其含义也由其操作数类型决定。
但是,在 C++ 中,某些类型之间存在相关的依赖关系。若两种类型相关,则可在需要某种类型的操作数位置上,
使用该类型的相关类型对象或值。如果两个类型之间可以相互转换,则称这两个类型相关;

5.12.4. 显式转换
显式转换也称为强制类型转换(cast),包括以下列名字命名的强制类型转换操作符:
static_cast、dynamic_cast、const_cast 和 reinterpret_cast。

5.12.6. 命名的强制类型转换
命名的强制类型转换符号的一般形式如下:
cast-name<type>(expression);

其中 
cast-name 为 static_cast、dynamic_cast、const_cast 和reinterpret_cast 之一,
type 为转换的目标类型,
expression 则是被强制转换的值。


强制转换的类型指定了在 expression 上执行某种特定类型的转换。
dynamic_cast


dynamic_cast 支持运行时识别指针或引用所指向的对象。
对 dynamic_cast的讨论将在第 18.2 节中进行。


const_cast
const_cast ,顾名思义,将转换掉表达式的 const 性质。
例如,假设有函数 string_copy,只有唯一的参数,为 char* 类型,我们对该函数只读不写。
在访问该函数时,最好的选择是修改它让它接受 const char* 类型的参数。


如果不行,可通过 const_cast 用一个 const 值调用 string_copy 函数:
char *pc = string_copy(const_cast<char*>(pc_str));


只有使用 const_cast 才能将 const 性质转换掉。
在这种情况下,试图使用其他三种形式的强制转换都会导致编译时的错误。
类似地,除了添加或删除const 特性,用 const_cast 符来执行其他任何类型转换,都会引起编译错误。


static_cast


编译器隐式执行的任何类型转换都可以由 static_cast 显式完成:
double d = 97.0;
// cast specified to indicate that the conversion is intentional
char ch = static_cast<char>(d);
const char *pc_str;


reinterpret_cast
reinterpret_cast 通常为操作数的位模式提供较低层次的重新解释。
reinterpret_cast 本质上依赖于机器。为了安全地使用
reinterpret_cast,要求程序员完全理解所涉及的数据类型,以及编译器实现强制类型转换的细节。