表达式由一个或多个操作数通过操作符组合而成。最简单的表达式仅包含一个字面值常量或变量。较复杂的表达式则由操作符以及一个或多个操作数构成。
每个表达式都会产生一个结果。如果表达式中没有操作符,则其结果就是操作数本身的值。当一个对象用在需要使用其值的地方,则计算该对象的值。
操作符的含义——该操作符执行什么操作以及操作结果的类型——取决于操作数的类型。
C++提供了一元操作符和二元操作符两种操作符。作用在一个操作数上的操作符称为一元操作符。如曲地址符(&)和解引用操作符(*);而二元操作符则作用于两个操作数上,如加法操作符(+)和减法操作符(-)。
一、算术操作符
当只有一个操作数为负数时,求模操作结果值的符号可依据分子(被除数)或分母(除数)的符号而定。如果求模的结果随分子的符号,则除出来的值想零一侧取整;如果求模与分母的的符号匹配,则除出来的值向负无穷一侧取整。
二、关系操作符和逻辑操作符
操作符 | 功能 |
! | 逻辑非 |
< <= > >= |
小于
小于等于 大于 大于等于 |
== != |
等于
不等 |
&& | 逻辑与 |
|| | 逻辑或 |
2.1 逻辑与、逻辑或操作符
仅当逻辑与(&&)操作符的两个操作数都为true,其结果才得true。
对于逻辑或(||)操作符,只要两个操作数之一为true,它的值就为true。
注:逻辑与和逻辑或操作符总是先计算做操作数,然后再计算其右操作数。只有在仅靠左操作数的值无法确定该逻辑表达式的结果时,才会求解其右操作数。我们常常称这种求值策略为“短路求值”。
2.2 不应该串接使用关系操作符
三、位操作符
位操作符使用整型的操作数。位操作符将其整型操作数视为二进制位的集合,为每一位提供检验和设置的功能。
操作符 | 功能 |
~ | 位求反 |
<<
>> |
左移
右移 |
& | 位与 |
^ | 位异或 |
| | 位或 |
位操作符操纵的整数的类型可以是有符号的也可以是无符号的。如果 操作数为负数,则位操作符如何处理其操作数的符号位依赖于机器。于是它们的应用可能不同:在一个应用环境中实现的程序可能无法用于另外一应用环境。
对于位操作符,由于系统不确保如何处理其操作数的符号位,所以强烈建议使用unsigned整型操作数。
左移操作符(<<)在右边插入0以补充空位。对于右移操作符(>>),如果其操作数是无符号位,则从左边开始插入0;如果操作数是有符号位,则插入符号位的副本或者0值,如何选择需依据具体的实现而定。移位操作的右操作数不可以是负数,而且必须是严格小于左操作数位数的值。否则,操作的效果未定义。
3.1 bitset对象或整型值的使用
3.2 将移位操作符用于IO
四、 赋值操作符
复制操作数的左操作数必须是非const的左值。
五、自增和自减操作符
六、sizeof操作符
sizeof操作符的作用是返回一个对象或类型名的长度,返回值的类型为size_t,长度的单位是字节。sizeof表达式的结果时编译时常量,该操作符有以下三种语法形式:
sizeof(type name);
sizeof ( expr);
sizeof expr;
将sizeof应用在表达式expr上,将获得该表达式的结果的类型长度。
将sizeof用于expr时,并没有计算表达式expr的值。特别是在sizeof *p中,指针p可以持有一个无效地址,因为不需要对p做解引用操作。
使用sizeof的结果部分地依赖所涉及的类型:
1、对char类型或值为char类型的的表达式做sizeof操作保证得1;
2、对引用类型做sizeof操作将返回存放此引用类型对象所需的内存空间的大小。
3、对指针做sizeof操作将返回存放指针所需的内存的大小;注意,如果要获取该指针所指向对象的大小,则必须对该指针进行解引用。
4、对数组做sizeof操作等效于将对其元素类型做sizeof操作的结果乘上数组元素的个数。
七、new和delete表达式
定义变量时,必须指定其数据类型和名字。而动态创建对象时,只需要指定其数据类型,而不必为该对象命名。取而代之的是,new表达式返回指向新创建对象的指针,我们通过该指针来访问此对象。
7.1 动态创建对象的初始化
动态创建对象可用初始化变量的方式实现初始化:
int i(1024);
int *pi = new int(1024);
string s(10, ‘9‘);
string *ps = new string(10. ‘9‘);
C++使用直接初始化语法规则初始化动态创建的对象。如果提供了初值,new表达式分配到所需要的内存后,用给定的初值初始化该内存空间。
7.2 动态创建对象的默认初始化
如果不提供显示初始化,动态创建的对象与函数内定义的变量初始化方式相同。对于类类型的对象,用该类的默认构造函数初始化;而内置类型的对象则无初始化。
7.3 耗尽内存
如果new表达式无法获取需要的内存空间,系统将抛出名为bad_alloc的异常。
7.4 撤销动态创建的对象
动态创建的对象用完后,程序员必须显示地将该对象占用的内存返回给自由存储区。C++提供了delete表达式释放指针所指向的地址空间。
7.5 零值指针的删除
如果指针的值为0.则在其上做delete操作是合法的,但这样没有任何意义:
int *ip = 0;
delete ip;
C++保证:删除0值的指针是安全的。
7.6 在delete之后,重设指针的值
删除指针后,该指针变成悬垂指针。悬垂指针指向曾经存放对象的内存,但该对象已经不再存在了。悬垂指针往往导致程序错误,而且很难检测出来。
一旦删除了指针所指向的对象,立即将指针置为0,这样就非常清楚地表明指针不再指向任何对象。
7.7 const对象的动态分配和回收
const int *pci = new const int(1024);
与其他常量一样,动态创建const对象必须在创建时初始化,并且一经初始化,其值就不能在修改。
警告:动态内存的管理容易出错
下面三种常见的程序错误都与动态内存分配相关:
(1)、删除(delete)指向动态分配内存的指针失败,因而无法将该块内存返还给自由存储区。删除动态分配内存失败称为“内存泄漏(memory leak)”。内存泄漏很难发现,一般需等应用程序运行了一段时间后,耗尽了所有的内存空间,内存泄漏才会显露出来。
(2)、读写已删除的对象。如果删除指针所指向的对象之后,将指针置为0值,则比较容易检测出这类错误。
(3)、对同一个内存空间使用两次delete表达式。当两个指针指向同一个动态创建的对象,删除时就会发生错误。如果在其中一个指针上做delete运算,将该对象的内存空间返还非自由存储区,然后接着delete第二个指针,此时则自由存储区可能会被破坏。
7.8 删除const对象
delete pci;
即使delete表达式的操作数是指向int型const对象的指针,该语句同样有效回收pci所指向的内容。
八、类型转换
表达式是否合法取决于操作数的类型,而且合法的表达式其含义也是由操作数类型决定。
如果这两个类型之间可以相互转换,则称这两个类型相关。
C++并不是把两个不同类型的值直接加在一起,而是提供了一组转换规则,以便在执行算术操作之前,将两个操作数转换为同一种数据类型。这些转换规则由编译器自动执行,无需程序员介入。因此,他们也被称为隐式类型转换。
8.1 何时发生隐式类型转换
编译器在必要时将类型转换规则应用到内置类型和类类型的对象上。在下列 情况下,将发生隐式类型转换:
1、在混合类型表达式中,其操作数被转换为相同的类型:
int ival;
double dval;
ival >= dval;//ival converted to double
2、用作条件的表达式被转换为bool类型:
3、用一表达式初始化某个变量,或将一表达式赋值给某个变量,则该表达式被转换为该变量的类型
8.2 算术转换
8.2.1 有符号与无符号类型之间的转换
若表达式中使用了无符号数值,所定义的转换规则需保护操作数的精度。unsigned 操作数的转换依赖于机器中整型的相对大小,因此,这类转换本质上依赖于机器。
8.3 其他隐式转换
8.3.1 指针转换
在使用数组时,大多数情况下数组都会自动转换为指向第一个元素的指针:
int ia[10];
int *ip = ia;
不将数组转换为指针的例外情况有:数组用作取地址(&)操作符的操作数或sizeof操作符的操作数时,或用数组对数组的引用进行初始化时,不会将数组转换为指针。
C++还提供了另外两种指针转换:指向任意数据类型的指针都可转换为void*类型;整型值常量0可转换为任意指针类型。
8.3.2 转换为bool类型
8.3.3 算术类型与bool类型的转换
8.3.4 转换与枚举类型
C++自动将枚举类型的对象或枚举成员转换为整型,其转换结果可用于任何要求使用整数值的地方。
九、显示转换
显示转换称为强制类型转换(cast),包括以下列名字命名的强制类型转换操作符:
static_cast、dynamic_cast、const_cast和reinterpret_cast。
9.1 何时需要强制类型转换
因为要覆盖通常的标准转换,所以需显示使用强制类型转换。
显示使用强制类型转换的另一个原因是:可能存在多种转换时,需要选择一种特定的类型转换。
9.2 命名的强制类型转换