C++ 表达式
2015-11-23 21:46
441 查看
《C++ Primer 4th》读书摘要
C++ 提供了丰富的操作符,并定义操作数为内置类型时,这些操作符的含义。除此之外,C++ 还支持操作符重载,允许程序员自定义用于类类型时操作符的含义。标准库正是使用这种功能定义用于库类型的操作符。
操作符的含义——该操作符执行什么操作以及操作结果的类型——取决于操作数的类型。除非已知道操作数的类型,否则无法确定一个特定表达式的含义。
按优先级来对操作符进行分组——一元操作符优先级最高,其次是乘、除操作,接着是二元的加、减法操作。高优先级的操作符要比低优先级的结合得更紧密。这些算术操作符都是左结合,这就意味着当操作符的优先级相同时,这些操作符从左向右依次与操作数结合。
逻辑与和逻辑或操作符总是先计算其左操作数,然后再计算其右操作数。只有在仅靠左操作数的值无法确定该逻辑表达式的结果时,才会求解其右操作数。我们常常称这种求值策略为“短路求值(short-circuit evaluation)”。
由于 true 转换为 1,因此要检测某值是否与 bool 字面值true 相等,其等效判断条件通常很难正确编写:如果 val 不是 bool 值,val 和 true 的比较等效于:
if (val == 1) { /* ... */ }
这与下面的条件判断完全不同:
// condition succeeds if val is any nonzero value
if (val) { /* ... */ }
此时,只要 val 为任意非零值,条件判断都得 true。如果显式地书写条件比较,则只有当 val 等于指定的 1 值时,条件才成立。
位操作符使用整型的操作数。位操作符将其整型操作数视为二进制位的集合,为每一位提供检验和设置的功能。另外,这类操作符还可用于bitset 类型的操作数,该类型具有这里所描述的整型操作数的行为。
移位操作的右操作数不可以是负数,而且必须是严格小于左操作数位数的值。否则,操作的效果未定义。位异或(互斥或,exclusive or)操作符(^)也需要两个整型操作数。在每个位的位置,如果两个操作数对应的位只有一个(不是两个)为 1,则操作结果中该位为 1,否则为 0。
一般而言,标准库提供的 bitset 操作更直接、更容易阅读和书写、正确使用的可能性更高。而且,bitset 对象的大小不受 unsigned 数的位数限制。通常来说,bitset 优于整型数据的低级直接位操作。
输入输出标准库(IO library)分别重载了位操作符 >> 和 << 用于输入和输出。移位操作符具有中等优先级:其优先级比算术操作符低,但比关系操作符、赋值操作符和条件操作符优先级高。
与其他二元操作符不同,赋值操作具有右结合特性。当表达式含有多个赋值操作符时,从右向左结合。多个赋值操作中,各对象必须具有相同的数据类型,或者具有可转换为同一类型的数据类型。
复合赋值操作符的一般语法格式为:
a op= b;
其中,op= 可以是下列十个操作符之一:
+= -= *= /= %= // arithmetic operators
<<= >>= &= ^= |= // bitwise operators
这两种语法形式存在一个显著的差别:使用复合赋值操作时,左操作数只计算了一次;而使用相似的长表达式时,该操作数则计算了两次,第一次作为右操作数,而第二次则用做左操作数。除非考虑可能的性能价值,在很多(可能是大部分的)上下文环境里这个差别不是本质性的。
自增操作使其操作数加1,操作结果是修改后的值。它的后置形式同样对其操作数加 1(或减 1),但操作后产生操作数原来的、未修改的值作为表达式的结果:
int i = 0, j;
j = ++i; // j = 1, i = 1: prefix yields incremented value
j = i++; // j = 1, i = 2: postfix yields unincremented value
因为前置操作返回加1 后的值,所以返回对象本身,这是左值。而后置操作返回的则是右值。
建议:只有在必要时才使用后置操作符
有使用 C 语言背景的读者可能会觉得奇怪,为什么要在程序中使用前自增操作。道理很简单:因为前置操作需要做的工作更少,只需加 1 后返回加 1 后的结果即可。而后置操作符则必须先保存操作数原来的值,以便返回未加 1 之前的值作为操作的结果。对于 int 型对象和指针,编译器可优化掉这项额外工作。但是对于更多的复杂迭代器类型,这种额外工作可能会花费更大的代价。因此,养成使用前置操作这个好习惯,就不必操心性能差异的问题。
vector<int>::iterator iter = ivec.begin();
// prints 10 9 8 ... 1
while (iter != ivec.end())
cout << *iter++ << endl; // iterator postfix increment
由于后自增操作的优先级高于解引用操作,因此 *iter++ 等效于*(iter++)。子表达式 iter++ 使 iter 加 1,然后返回 iter 原值的副本作为该表达式的结果。因此,解引用操作 * 的操作数是 iter 未加 1 前的副本。
C++ 语言为包含点操作符和解引用操作符的表达式提供了一个同义词:箭头操作符(->)。点操作符用于获取类类型对象的成员
假设有一个指向类类型对象的指针(或迭代器),下面的表达式相互等价:
(*p).foo; // dereference p to get an object and fetch its member named foo
p->foo; // equivalent way to fetch the foo from the object to which p points
sizeof 操作符的作用是返回一个对象或类型名的长度,返回值的类型为size_t,长度的单位是字节。
以什么次序求解操作数通常没有多大关系。只有当操作符的两个操作数涉及到同一个对象,并改变其值时,操作数的
计算次序才会影响结果。如果一个子表达式修改了另一个子表达式的操作数,则操作数的求解次序就变得相当重要:
// oops! language does not define order of evaluation
if (ia[index++] < ia[index])
此表达式的行为没有明确定义。问题在于:< 操作符的左右操作数都使用了index 变量,但是,左操作数更改了该变量的值。假设 index 初值为 0,编译器可以用下面两种方式之一求该表达式的值:
if (ia[0] < ia[0]) // execution if rhs is evaluated first
if (ia[0] < ia[1]) // execution if lhs is evaluated first
一个表达式里,不要在两个或更多的子表达式中对同一对象做自增或自减操作。
下面两个指导原则有助于处理复合表达式:
1. 如果有怀疑,则在表达式上按程序逻辑要求使用圆括号强制操作数的组合。
2. 如果要修改操作数的值,则不要在同一个语句的其他地方使用该操作数。如果必须使用改变的值,则把该表达式分割成两个独立语句:在一个语句中改变该操作数的值,再在下一个语句使用它。
第二个规则有一个重要的例外:如果一个子表达式修改操作数的值,然后将该子表达式的结果用于另一个子表达式,这样则是安全的
而动态创建对象时,只需指定其数据类型,而不必为该对象命名。取而代之的是,new 表达式返回指向新创建对象的指针,我们通过该指针来访问此对象:
int i; // named, uninitialized int variable
int *pi = new int; // pi points to dynamically allocated,unnamed, uninitialized int
如果不提供显式初始化,动态创建的对象与在函数内定义的变量初始化方式相同。对于类类型的对象,用该类的默认构造函数初始化;而内置类型的对象则无初始化。
string *ps = new string; // initialized to empty string
int *pi = new int; // pi points to an uninitialized int
正如我们(几乎)总是要初始化定义为变量的对象一样,在动态创建对象时,(几乎)总是对它做初始化也是一个好办法。
int *pi = new int; // pi points to an uninitialized int
int *pi = new int(); // pi points to an int value-initialized to 0
动态创建的对象用完后,程序员必须显式地将该对象占用的内存返回给自由存储区。C++ 提供了 delete 表达式释放指针所指向的地址空间。
delete pi;
如果指针指向不是用 new 分配的内存地址,则在该指针上使用delete 是不合法的。
删除指针后,该指针变成悬垂指针。悬垂指针指向曾经存放对象的内存,但该对象已经不再存在了。悬垂指针往往导致程序错误,而且很难检测出来。
一旦删除了指针所指向的对象,立即将指针置为 0,这样就非常清楚地表明指针不再指向任何对象。
C++ 允许动态创建 const 对象:
// allocate and initialize a const object
const int *pci = new const int(1024);
与其他常量一样,动态创建的 const 对象必须在创建时初始化,并且一经初始化,其值就不能再修改
警告:动态内存的管理容易出错
下面三种常见的程序错误都与动态内存分配相关:
1. 删除( delete )指向动态分配内存的指针失败,因而无法将该块内存返还给自由存储区。删除动态分配内存失败称为“内存泄漏(memory leak)”。内存泄漏很难发现,一般需等应用程序运行了一段时间后,耗尽了所有内存空间时,内存泄漏才会显露出来。
2. 读写已删除的对象。如果删除指针所指向的对象之后,将指针置为 0 值,则比较容易检测出这类错误。
3. 对同一个内存空间使用两次 delete 表达式。当两个指针指向同一个动态创建的对象,删除时就会发生错误。如果在其中一个指针上做 delete 运算,将该对象的内存空间返还给自由存储区,然后接着 delete 第二个指针,此时则自由存储区可能会被破坏。
尽管程序员不能改变 const 对象的值,但可撤销对象本身。如同其他动态对象一样, const 动态对象也是使用删除指针来释放的:
delete pci; // ok: deletes a const object
如果两个类型之间可以相互转换,则称这两个类型相关。
包含 short 和 int 类型的表达式, short 类型的值转换为 int 。如果int 型足够表示所有 unsigned short 型的值,则将 unsigned short 转换为int,否则,将两个操作数均转换为 unsigned int 。
指向任意数据类型的指针都可转换为void* 类型;整型数值常量 0 可转换为任意指针类型。
算术值和指针值都可以转换为 bool 类型。如果指针或算术值为 0,则其bool 值为 false ,而其他值则为 true
当使用非 const 对象初始化 const 对象的引用时,系统将非 const 对象转换为 const 对象。此外,还可以将非 const 对象的地址(或非 const 指针)转换为指向相关 const 类型的指针:
int i;
int ci = 0;
const int &j = i; // ok: convert non-const to reference to const int
const int *p = &ci; // ok: convert address of non-const to address of a const
显式转换也称为强制类型转换(cast),包括以下列名字命名的强制类型转换操作符:static_cast、dynamic_cast、const_cast 和 reinterpret_cast。
虽然有时候确实需要强制类型转换,但是它们本质上是非常危险的。显式使用强制类型转换的另一个原因是:可能存在多种转换时,需要选择一种特定的类型转换。
命名的强制类型转换符号的一般形式如下:
cast-name<type>(expression);
其中 cast-name 为 static_cast、dynamic_cast、const_cast 和reinterpret_cast 之一,type 为转换的目标类型,而 expression 则是被强制转换的值。强制转换的类型指定了在 expression 上执行某种特定类型的转换。
const_cast ,顾名思义,将转换掉表达式的 const 性质.只有使用 const_cast 才能将 const 性质转换掉。在这种情况下,试图使用其他三种形式的强制转换都会导致编译时的错误。类似地,除了添加或删除const 特性,用 const_cast 符来执行其他任何类型转换,都会引起编译错误。
const char *pc_str;
char *pc = string_copy(const_cast<char*>(pc_str));
编译器隐式执行的任何类型转换都可以由 static_cast 显式完成:
double d = 97.0;
// cast specified to indicate that the conversion is intentional
char ch = static_cast<char>(d);
如果编译器不提供自动转换,使用 static_cast 来执行类型转换也是很有用的。例如,下面的程序使用 static_cast 找回存放在 void* 指针中的值:
void* p = &d; // ok: address of any data object can be stored in avoid*
// ok: converts void* back to the original pointer type
double *dp = static_cast<double*>(p);
可通过 static_cast 将存放在 void* 中的指针值强制转换为原来的指针类型,此时我们应确保保持指针值。
建议:避免使用强制类型转换
强制类型转换关闭或挂起了正常的类型检查(第 2.3 节)。强烈建议程序员避免使用强制类型转换,不依赖强制类型转换也能写出很好的 C++程序。
这个建议在如何看待 reinterpret_cast 的使用时非常重要。此类强制转换总是非常危险的。相似地,使用 const_cast 也总是预示着设计缺陷。设计合理的系统应不需要使用强制转换抛弃 const 特性。其他的强制转换,如 static_cast 和 dynamic_cast,各有各的用途,但都不应频繁使用。每次使用强制转换前,程序员应该仔细考虑是否还有其他不
同的方法可以达到同一目的。如果非强制转换不可,则应限制强制转换值的作用域,并且记录所有假定涉及的类型,这样能减少错误发生的机会。
旧式强制类型转换
在引入命名的强制类型转换操作符之前,显式强制转换用圆括号将类型括起来实现:
char *pc = (char*) ip;
虽然标准 C++ 仍然支持旧式强制转换符号,但是我们建议,只有在 C 语言或标准 C++ 之前的编译器上编写代码时,才使用这种语法。
旧式强制转换符号有下列两种形式:
type (expr); // Function-style cast notation
(type) expr; // C-language-style cast notation
C++ 提供了丰富的操作符,并定义操作数为内置类型时,这些操作符的含义。除此之外,C++ 还支持操作符重载,允许程序员自定义用于类类型时操作符的含义。标准库正是使用这种功能定义用于库类型的操作符。
操作符的含义——该操作符执行什么操作以及操作结果的类型——取决于操作数的类型。除非已知道操作数的类型,否则无法确定一个特定表达式的含义。
按优先级来对操作符进行分组——一元操作符优先级最高,其次是乘、除操作,接着是二元的加、减法操作。高优先级的操作符要比低优先级的结合得更紧密。这些算术操作符都是左结合,这就意味着当操作符的优先级相同时,这些操作符从左向右依次与操作数结合。
逻辑与和逻辑或操作符总是先计算其左操作数,然后再计算其右操作数。只有在仅靠左操作数的值无法确定该逻辑表达式的结果时,才会求解其右操作数。我们常常称这种求值策略为“短路求值(short-circuit evaluation)”。
由于 true 转换为 1,因此要检测某值是否与 bool 字面值true 相等,其等效判断条件通常很难正确编写:如果 val 不是 bool 值,val 和 true 的比较等效于:
if (val == 1) { /* ... */ }
这与下面的条件判断完全不同:
// condition succeeds if val is any nonzero value
if (val) { /* ... */ }
此时,只要 val 为任意非零值,条件判断都得 true。如果显式地书写条件比较,则只有当 val 等于指定的 1 值时,条件才成立。
位操作符使用整型的操作数。位操作符将其整型操作数视为二进制位的集合,为每一位提供检验和设置的功能。另外,这类操作符还可用于bitset 类型的操作数,该类型具有这里所描述的整型操作数的行为。
操作符 | 功能 | 用法 |
~ | bitwise NOT(位求反) | ~expr |
<< | left shift(左移) | expr1 << expr2 |
>> | right shift(右移) | expr1 >> expr2 |
& | bitwise AND(位与) | expr1 & expr2 |
^ | bitwise XOR(位异或) | expr1 ^ expr2 |
| | bitwise OR(位或) | expr1 | expr2 |
一般而言,标准库提供的 bitset 操作更直接、更容易阅读和书写、正确使用的可能性更高。而且,bitset 对象的大小不受 unsigned 数的位数限制。通常来说,bitset 优于整型数据的低级直接位操作。
输入输出标准库(IO library)分别重载了位操作符 >> 和 << 用于输入和输出。移位操作符具有中等优先级:其优先级比算术操作符低,但比关系操作符、赋值操作符和条件操作符优先级高。
与其他二元操作符不同,赋值操作具有右结合特性。当表达式含有多个赋值操作符时,从右向左结合。多个赋值操作中,各对象必须具有相同的数据类型,或者具有可转换为同一类型的数据类型。
复合赋值操作符的一般语法格式为:
a op= b;
其中,op= 可以是下列十个操作符之一:
+= -= *= /= %= // arithmetic operators
<<= >>= &= ^= |= // bitwise operators
这两种语法形式存在一个显著的差别:使用复合赋值操作时,左操作数只计算了一次;而使用相似的长表达式时,该操作数则计算了两次,第一次作为右操作数,而第二次则用做左操作数。除非考虑可能的性能价值,在很多(可能是大部分的)上下文环境里这个差别不是本质性的。
自增操作使其操作数加1,操作结果是修改后的值。它的后置形式同样对其操作数加 1(或减 1),但操作后产生操作数原来的、未修改的值作为表达式的结果:
int i = 0, j;
j = ++i; // j = 1, i = 1: prefix yields incremented value
j = i++; // j = 1, i = 2: postfix yields unincremented value
因为前置操作返回加1 后的值,所以返回对象本身,这是左值。而后置操作返回的则是右值。
建议:只有在必要时才使用后置操作符
有使用 C 语言背景的读者可能会觉得奇怪,为什么要在程序中使用前自增操作。道理很简单:因为前置操作需要做的工作更少,只需加 1 后返回加 1 后的结果即可。而后置操作符则必须先保存操作数原来的值,以便返回未加 1 之前的值作为操作的结果。对于 int 型对象和指针,编译器可优化掉这项额外工作。但是对于更多的复杂迭代器类型,这种额外工作可能会花费更大的代价。因此,养成使用前置操作这个好习惯,就不必操心性能差异的问题。
vector<int>::iterator iter = ivec.begin();
// prints 10 9 8 ... 1
while (iter != ivec.end())
cout << *iter++ << endl; // iterator postfix increment
由于后自增操作的优先级高于解引用操作,因此 *iter++ 等效于*(iter++)。子表达式 iter++ 使 iter 加 1,然后返回 iter 原值的副本作为该表达式的结果。因此,解引用操作 * 的操作数是 iter 未加 1 前的副本。
C++ 语言为包含点操作符和解引用操作符的表达式提供了一个同义词:箭头操作符(->)。点操作符用于获取类类型对象的成员
假设有一个指向类类型对象的指针(或迭代器),下面的表达式相互等价:
(*p).foo; // dereference p to get an object and fetch its member named foo
p->foo; // equivalent way to fetch the foo from the object to which p points
sizeof 操作符的作用是返回一个对象或类型名的长度,返回值的类型为size_t,长度的单位是字节。
结合性 | 操作符 | 功能 | 用法 |
L | :: | global scope(全局作用域) | :: name |
L | :: | class scope(类作用域) | class :: name |
L | :: | namespace scope(名字空间作用域) | namespace :: name |
L | . | member selectors(成员选择) | object . member |
L | -> | member selectors(成员选择) | pointer -> member |
L | [] | subscript(下标) | variable [ expr ] |
L | () | function call(函数调用) | name (expr_list) |
L | () | type construction(类型构造) | type (expr_list) |
R | ++ | postfix increment(后自增操作) | lvalue++ |
R | -- | postfix decrement(后自减操作) | lvalue-- |
R | Typeid | type ID(类型 ID) | typeid (type) |
R | typeid | run-time type ID(运行时类型 ID) | typeid (expr) |
R | explicit cast(显式强制类型转换) | type conversion(类型转换) | cast_name<type>(expr) |
R | sizeof | size of object(对象的大小) | sizeof expr |
R | sizeof | size of type(类型的大小) | sizeof(type) |
R | ++ | prefix increment(前自增操作) | ++ lvalue |
R | -- | prefix decrement(前自减操作) | -- lvalue |
R | ~ | bitwise NOT(位求反) | ~expr |
R | ! | logical NOT(逻辑非) | !expr |
R | unary minus(一元负号) | -expr | |
R | + | unary plus(一元正号) | +expr |
R | * | dereference(解引用) | *expr |
R | & | address-of(取地址) | &expr |
R | () | type conversion(类型转换) | (type) expr |
R | new | allocate object(创建对象) | new type |
R | delete | deallocate object(释放对象) | delete expr |
R | delete[] | deallocate array(释放数组) | delete[] expr |
L | ->* | ptr to member select(指向成员操作的指针) | ptr ->* ptr_to_member |
L | .* | ptr to member select(指向成员操作的指针) | obj .*ptr_to_member |
L | * | multiply(乘法) | expr * expr |
L | / | divide(除法) | expr / expr |
L | % | modulo (remainder)(求模(求余)) | expr % expr |
L | + | add(加法) | expr + expr |
L | subtract(减法) | expr - expr | |
L | << | bitwise shift left(位左移) | expr << expr |
L | >> | bitwise shift right(位右移) | expr >> expr |
L | < | less than(小于) | expr < expr |
L | <= | less than or equal(小于或等于) | expr <= expr |
L | > | greater than(大于) | expr > expr |
L | >= | greater than or equal(大于或等于) | expr >= expr |
L | == | equality(相等) | expr == expr |
L | != | inequality(不等) | expr != expr |
L | & | bitwise AND(位与) | expr & expr |
L | ^ | bitwise XOR() | expr ^ expr |
L | | | bitwise OR(位异或) | expr | expr |
L | && | logical AND(逻辑与) | expr && expr |
L | || | logical OR(逻辑或) | expr || expr |
R | ?: | conditional(条件操作) | expr ? expr : expr |
R | = | assignment(赋值操作) | lvalue = expr |
R | *=, /=, %=, +=, -=, <<=, >>=, &=,|=, ^= | compound assign(复合赋值操作) | lvalue += expr, etc. |
R | throw | throw exception(抛出异常) | throw expr |
L | , | comma(逗号) expr , | expr |
计算次序才会影响结果。如果一个子表达式修改了另一个子表达式的操作数,则操作数的求解次序就变得相当重要:
// oops! language does not define order of evaluation
if (ia[index++] < ia[index])
此表达式的行为没有明确定义。问题在于:< 操作符的左右操作数都使用了index 变量,但是,左操作数更改了该变量的值。假设 index 初值为 0,编译器可以用下面两种方式之一求该表达式的值:
if (ia[0] < ia[0]) // execution if rhs is evaluated first
if (ia[0] < ia[1]) // execution if lhs is evaluated first
一个表达式里,不要在两个或更多的子表达式中对同一对象做自增或自减操作。
下面两个指导原则有助于处理复合表达式:
1. 如果有怀疑,则在表达式上按程序逻辑要求使用圆括号强制操作数的组合。
2. 如果要修改操作数的值,则不要在同一个语句的其他地方使用该操作数。如果必须使用改变的值,则把该表达式分割成两个独立语句:在一个语句中改变该操作数的值,再在下一个语句使用它。
第二个规则有一个重要的例外:如果一个子表达式修改操作数的值,然后将该子表达式的结果用于另一个子表达式,这样则是安全的
而动态创建对象时,只需指定其数据类型,而不必为该对象命名。取而代之的是,new 表达式返回指向新创建对象的指针,我们通过该指针来访问此对象:
int i; // named, uninitialized int variable
int *pi = new int; // pi points to dynamically allocated,unnamed, uninitialized int
如果不提供显式初始化,动态创建的对象与在函数内定义的变量初始化方式相同。对于类类型的对象,用该类的默认构造函数初始化;而内置类型的对象则无初始化。
string *ps = new string; // initialized to empty string
int *pi = new int; // pi points to an uninitialized int
正如我们(几乎)总是要初始化定义为变量的对象一样,在动态创建对象时,(几乎)总是对它做初始化也是一个好办法。
int *pi = new int; // pi points to an uninitialized int
int *pi = new int(); // pi points to an int value-initialized to 0
动态创建的对象用完后,程序员必须显式地将该对象占用的内存返回给自由存储区。C++ 提供了 delete 表达式释放指针所指向的地址空间。
delete pi;
如果指针指向不是用 new 分配的内存地址,则在该指针上使用delete 是不合法的。
删除指针后,该指针变成悬垂指针。悬垂指针指向曾经存放对象的内存,但该对象已经不再存在了。悬垂指针往往导致程序错误,而且很难检测出来。
一旦删除了指针所指向的对象,立即将指针置为 0,这样就非常清楚地表明指针不再指向任何对象。
C++ 允许动态创建 const 对象:
// allocate and initialize a const object
const int *pci = new const int(1024);
与其他常量一样,动态创建的 const 对象必须在创建时初始化,并且一经初始化,其值就不能再修改
警告:动态内存的管理容易出错
下面三种常见的程序错误都与动态内存分配相关:
1. 删除( delete )指向动态分配内存的指针失败,因而无法将该块内存返还给自由存储区。删除动态分配内存失败称为“内存泄漏(memory leak)”。内存泄漏很难发现,一般需等应用程序运行了一段时间后,耗尽了所有内存空间时,内存泄漏才会显露出来。
2. 读写已删除的对象。如果删除指针所指向的对象之后,将指针置为 0 值,则比较容易检测出这类错误。
3. 对同一个内存空间使用两次 delete 表达式。当两个指针指向同一个动态创建的对象,删除时就会发生错误。如果在其中一个指针上做 delete 运算,将该对象的内存空间返还给自由存储区,然后接着 delete 第二个指针,此时则自由存储区可能会被破坏。
尽管程序员不能改变 const 对象的值,但可撤销对象本身。如同其他动态对象一样, const 动态对象也是使用删除指针来释放的:
delete pci; // ok: deletes a const object
如果两个类型之间可以相互转换,则称这两个类型相关。
包含 short 和 int 类型的表达式, short 类型的值转换为 int 。如果int 型足够表示所有 unsigned short 型的值,则将 unsigned short 转换为int,否则,将两个操作数均转换为 unsigned int 。
指向任意数据类型的指针都可转换为void* 类型;整型数值常量 0 可转换为任意指针类型。
算术值和指针值都可以转换为 bool 类型。如果指针或算术值为 0,则其bool 值为 false ,而其他值则为 true
当使用非 const 对象初始化 const 对象的引用时,系统将非 const 对象转换为 const 对象。此外,还可以将非 const 对象的地址(或非 const 指针)转换为指向相关 const 类型的指针:
int i;
int ci = 0;
const int &j = i; // ok: convert non-const to reference to const int
const int *p = &ci; // ok: convert address of non-const to address of a const
显式转换也称为强制类型转换(cast),包括以下列名字命名的强制类型转换操作符:static_cast、dynamic_cast、const_cast 和 reinterpret_cast。
虽然有时候确实需要强制类型转换,但是它们本质上是非常危险的。显式使用强制类型转换的另一个原因是:可能存在多种转换时,需要选择一种特定的类型转换。
命名的强制类型转换符号的一般形式如下:
cast-name<type>(expression);
其中 cast-name 为 static_cast、dynamic_cast、const_cast 和reinterpret_cast 之一,type 为转换的目标类型,而 expression 则是被强制转换的值。强制转换的类型指定了在 expression 上执行某种特定类型的转换。
const_cast ,顾名思义,将转换掉表达式的 const 性质.只有使用 const_cast 才能将 const 性质转换掉。在这种情况下,试图使用其他三种形式的强制转换都会导致编译时的错误。类似地,除了添加或删除const 特性,用 const_cast 符来执行其他任何类型转换,都会引起编译错误。
const char *pc_str;
char *pc = string_copy(const_cast<char*>(pc_str));
编译器隐式执行的任何类型转换都可以由 static_cast 显式完成:
double d = 97.0;
// cast specified to indicate that the conversion is intentional
char ch = static_cast<char>(d);
如果编译器不提供自动转换,使用 static_cast 来执行类型转换也是很有用的。例如,下面的程序使用 static_cast 找回存放在 void* 指针中的值:
void* p = &d; // ok: address of any data object can be stored in avoid*
// ok: converts void* back to the original pointer type
double *dp = static_cast<double*>(p);
可通过 static_cast 将存放在 void* 中的指针值强制转换为原来的指针类型,此时我们应确保保持指针值。
建议:避免使用强制类型转换
强制类型转换关闭或挂起了正常的类型检查(第 2.3 节)。强烈建议程序员避免使用强制类型转换,不依赖强制类型转换也能写出很好的 C++程序。
这个建议在如何看待 reinterpret_cast 的使用时非常重要。此类强制转换总是非常危险的。相似地,使用 const_cast 也总是预示着设计缺陷。设计合理的系统应不需要使用强制转换抛弃 const 特性。其他的强制转换,如 static_cast 和 dynamic_cast,各有各的用途,但都不应频繁使用。每次使用强制转换前,程序员应该仔细考虑是否还有其他不
同的方法可以达到同一目的。如果非强制转换不可,则应限制强制转换值的作用域,并且记录所有假定涉及的类型,这样能减少错误发生的机会。
旧式强制类型转换
在引入命名的强制类型转换操作符之前,显式强制转换用圆括号将类型括起来实现:
char *pc = (char*) ip;
虽然标准 C++ 仍然支持旧式强制转换符号,但是我们建议,只有在 C 语言或标准 C++ 之前的编译器上编写代码时,才使用这种语法。
旧式强制转换符号有下列两种形式:
type (expr); // Function-style cast notation
(type) expr; // C-language-style cast notation
相关文章推荐
- 【应用笔记】【AN003】VC++环境下基于以太网的4-20mA电流采集
- 二叉树的基本操作的c++实现
- C++ for_each学习笔记
- C/C++中void用法总结
- 数据抽象与封装的好处--【primer第四版】
- C/C++学习(二)输入n个整数,输出其中最小的k个。
- 合并两个有序数组(C++版)
- c++之判断栈的弹出是否合法
- c++之栈的顺序表实现
- C语言初学者画图练习
- C语言初学者简单语法综合练习
- C语言初学者简单语法小测
- c++栈之带括号的四则运算
- c++友元类及友元函数
- Python中的for与其他类C语言的比较(如c++,c#)
- 1、让自己习惯c++
- Python之自动化生成解析XML的C++类(基于tinyxml库解析)
- C++ Primer书中第二章经常用到的Sales_data类
- Effective C++读书笔记一
- Python之自动生成解析ini文件的C++类(基于libiniparser.a)