本 Wiki 开启了 HTTPS。但由于同 IP 的 Blog 也开启了 HTTPS,因此本站必须要支持 SNI 的浏览器才能浏览。为了兼容一部分浏览器,本站保留了 HTTP 作为兼容。如果您的浏览器支持 SNI,请尽量通过 HTTPS 访问本站,谢谢!
这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录前一修订版后一修订版 | 前一修订版 | ||
cs:programming:cpp:cpp_primer:4_expressions [2024/01/14 13:46] – 移除 - 外部编辑 (未知日期) 127.0.0.1 | cs:programming:cpp:cpp_primer:4_expressions [2024/09/22 13:25] (当前版本) – [除法、末除和符号] codinghare | ||
---|---|---|---|
行 1: | 行 1: | ||
+ | ======表达式====== | ||
+ | C++ Primer 笔记 第四章\\ | ||
+ | ---- | ||
+ | ====表达式基础==== | ||
+ | 运算符分为一元运算符(// | ||
+ | * //Operand Covension// | ||
+ | * //Operator overload// | ||
+ | ===左值和右值=== | ||
+ | 所有的表达式都被区分为**左值**(// | ||
+ | <WRAP center round tip 100%> | ||
+ | 通俗的来说,左值是明确有定义的,在内存中固定存在的对象(地址确定),而右值大多数代表临时对象(有内容,无确定地址) | ||
+ | </ | ||
+ | |||
+ | 运算符是根据需求左值/ | ||
+ | **当需求右值的时候(也就是希望使用内容而不是位置的时候),可以使用左值代替(此时使用左值中的**内容**),但是反过来不行(右值只能表示内容)。** \\ \\ | ||
+ | 之前见过的几个例子: | ||
+ | |||
+ | - 赋值运算中,“=” 运算左边如果是一个 non-const 的左值,那么表达式得到的结果类型也是左值。(比如 a = 10; 得到的是 a) | ||
+ | - 能取到地址的值都是左值,而取地址操作结果返回一个指针,是右值。 | ||
+ | - 解引用(*)运算,下标运算和迭代器的解引用,都产生左值。 | ||
+ | - 迭代器的自增 / 自减 也需要左值进行运算,< | ||
+ | ==decltype 的返回值类别== | ||
+ | '' | ||
+ | <code cpp> | ||
+ | int i = 0; | ||
+ | int *p = &i; | ||
+ | decltype(*p); | ||
+ | decltype(& | ||
+ | </ | ||
+ | ===优先级与结合律=== | ||
+ | 默认情况下复合表达式中的运算根据运算符的优先级和结合律来判断。使用括号可以强制改变优先级。\\ \\ | ||
+ | Ref: | ||
+ | |||
+ | ===表达式判定顺序=== | ||
+ | 需要注意的是,优先级和结合律只确定了 operands 与哪些运算符编组;但并没有确定组内 operands 运行的顺序。比如下例: | ||
+ | <code cpp> | ||
+ | int i = f1() + f2(); | ||
+ | </ | ||
+ | 可以看出 '' | ||
+ | <code cpp> | ||
+ | int i = 0; | ||
+ | cout << i << ++i << endl; //undefined | ||
+ | </ | ||
+ | 很显然 '' | ||
+ | <WRAP center round important 100%> | ||
+ | <wrap em> | ||
+ | </ | ||
+ | |||
+ | 有四种运算符是保证了 operands 的执行顺序:''&&'' | ||
+ | ==优先级/ | ||
+ | 很显然,优先级/ | ||
+ | <code cpp> | ||
+ | f() + g() * h() + j(); | ||
+ | </ | ||
+ | * 优先级指定 '' | ||
+ | * 结合律保证 '' | ||
+ | 至于先执行哪个函数,结合律和优先级是保证不了的。当这几个函数影响到同一个对象的时候,该表达式就是 Undefined 的。\\ \\ | ||
+ | 我们可以通过管理表达式的求值顺序来避免上述问题。一些 tips: | ||
+ | - 用括号保证优先级。 | ||
+ | - 如果改变了operand 的值,就不要将其放到同一个表达式的其他任何地方(例外:复合表达式中,虽然一个子表达式改变了 operand,但该表达式会作为另外一个子表达式的 operand 的时候,上述规则无效。比如 '' | ||
+ | |||
+ | ====算术运算符==== | ||
+ | 算术运算符的优先级可以同样可以参考P166。有几点需要注意的是: | ||
+ | - 一元运算符的优先级 > 二元乘除 > 二元加减。 | ||
+ | - 运算的时候都是从**左到右**(左结合律) | ||
+ | - 算术运算符得到的结果都是**右值**。 | ||
+ | ==bool 不能用于计算== | ||
+ | bool 类型的变量应该避免进行算术运算: | ||
+ | <code cpp> | ||
+ | bool b = true; | ||
+ | boob b2 = -b; | ||
+ | </ | ||
+ | 当 '' | ||
+ | ===算术运算的溢出=== | ||
+ | undefined 算数运算通常由两种情况导致:**数学上的无意义**和**计算机上的溢出(// | ||
+ | <code cpp> | ||
+ | short sv = 32767; //max value for 16bit shor | ||
+ | sv += 1; // overflow | ||
+ | </ | ||
+ | 这样的计算也是 undefined 的。 | ||
+ | ===除法、末除和符号=== | ||
+ | 整数除以整数的结果也是整数,小数部分会被抹掉。\\ \\ | ||
+ | 末除(// | ||
+ | <code cpp> | ||
+ | int iv = 42; | ||
+ | double dv =3.14; | ||
+ | iv % dv; //error | ||
+ | </ | ||
+ | C++11 中, 如果 '' | ||
+ | * (-m) / n 和 m / -n 均等于 -(m/n) | ||
+ | * m%(-n) 等于 m%n,而 (-m)%n = -(m%n) | ||
+ | |||
+ | |||
+ | |||
+ | ====逻辑 / 关系运算符==== | ||
+ | Operands 要求: | ||
+ | * **关系运算符**(// | ||
+ | * **逻辑运算符**(// | ||
+ | 返回值: | ||
+ | * 两者军返回 bool 类型,operand 值为 0,则结果为 '' | ||
+ | * 返回值均为**右值** | ||
+ | ===逻辑运算符=== | ||
+ | ==逻辑与和逻辑或== | ||
+ | * **逻辑与**(''&&'' | ||
+ | * **逻辑或**('' | ||
+ | ''&&'' | ||
+ | <code cpp> | ||
+ | /* e.g. && */ | ||
+ | index != s.size() && !isspace(s[index]); | ||
+ | /* e.g. ||, print newline when s empty or s not empty but hit the ' | ||
+ | for (const auto &s : text) { | ||
+ | cout << s; | ||
+ | if (s.empty() || s[s.size() - 1] == ' | ||
+ | cout << endl; | ||
+ | } | ||
+ | else | ||
+ | cout << " "; | ||
+ | } | ||
+ | </ | ||
+ | <WRAP center round tip 100%> | ||
+ | 因为 string 对象较大,因此一般使用引用访问会更效率;但因为只需要读,因此上面的例子的循环控制变量使用了 const auto &s 来定义。 | ||
+ | </ | ||
+ | ==逻辑非运算符== | ||
+ | 逻辑非('' | ||
+ | <code cpp> | ||
+ | if(!vec.empty()) // if vector is not empty | ||
+ | </ | ||
+ | |||
+ | ===关系运算符=== | ||
+ | 关系运算符(''<'' | ||
+ | <code cpp> | ||
+ | if (i < j < k) // i < j return a bool, then the condition is actually compring a bool with k | ||
+ | if (i < j && j < k) // if i < j and j < k. | ||
+ | </ | ||
+ | ===Equality Test & bool=== | ||
+ | bool 类型一个重要的用途是用于相等判断。C++ 中判断相等条件语句很简单: | ||
+ | <code cpp> | ||
+ | if(val) // true if val is not equal to 0 | ||
+ | if(!val) //true if val equals to 0 | ||
+ | </ | ||
+ | 值得说明的是,上述的条件变量 val 的类型会被隐式的转换成 bool 类型用作比较。很多人会这么写: | ||
+ | <code cpp> | ||
+ | if(val == true); | ||
+ | </ | ||
+ | 这样写的问题在于,如果 '' | ||
+ | <code cpp> | ||
+ | if(val == 1) | ||
+ | </ | ||
+ | 这和之前的条件几乎是完全不一样的。 | ||
+ | <WRAP center round important 100%> | ||
+ | 使用 bool literal (true / false) 作为比较的 operand 是不好的习惯。这些 Literal 应该只用于与类型为 bool 类型的变量作比较。 | ||
+ | </ | ||
+ | |||
+ | ====赋值运算符==== | ||
+ | <WRAP center round tip 100%> | ||
+ | 很多情况下需要考虑初始化与赋值的区别。 | ||
+ | </ | ||
+ | |||
+ | 赋值运算符有几个要点: | ||
+ | - 赋值运算符的左边必须是可以修改的左值。常量 / literal 都不能用。 | ||
+ | - 赋值结果的类型与左边的 operand **相同**,也是左值。 | ||
+ | - 赋值运算中如果左右两个 operand 的类型不同,右 operand 会转化成和左 operand 相同的类型,比如将浮点数赋值给整数,最后得到的是整数。 | ||
+ | ===List 初始化=== | ||
+ | C++11 的标准中, List initialization 是不能进行 narrowing conversion 的。因此,list 初始化要求赋值运算的两边类型和数量必须匹配: | ||
+ | <code cpp> | ||
+ | int k = {3.14}; //error, narrowing conversion | ||
+ | </ | ||
+ | 对于类的 List 初始化,如何进行要看类如何是如何重载赋值运算符的。 | ||
+ | |||
+ | ===赋值运算的结合律=== | ||
+ | 赋值运算的结合律是从右到左的。比如: | ||
+ | <code cpp> | ||
+ | int ival, jval; | ||
+ | ival = jval = 0; // expression will do jval = 0 first; then do ival = jval | ||
+ | </ | ||
+ | 多重赋值中,所有左边的 operand 的类型必须跟对应右边的 operand 匹配,或者是可以转换为同样的类型: | ||
+ | <code cpp> | ||
+ | int val, *pval; | ||
+ | ival = pval = 0;// error, pointer can't be converted to int | ||
+ | </ | ||
+ | ==赋值运算的优先级很低== | ||
+ | 赋值运算经常用于循环的判定中;由于其优先级很低,因此需要括号来保证优先级。比如下面的例子: | ||
+ | <code cpp> | ||
+ | int i = get_val(); | ||
+ | while(i != 42) { | ||
+ | i = get_val(); | ||
+ | } | ||
+ | </ | ||
+ | 很显然 '' | ||
+ | <code cpp> | ||
+ | int i = get_val(); | ||
+ | while((i = get_val()) | ||
+ | } | ||
+ | </ | ||
+ | ==复合赋值运算符== | ||
+ | 任意一种复合赋值运算符都等同于如下形式: | ||
+ | <code cpp> | ||
+ | a = a op b; | ||
+ | </ | ||
+ | 唯一的区别是,使用复合赋值运算符只求值一次,而普通形式会求值两次。 | ||
+ | ====自增自减运算符==== | ||
+ | 假设用自增运算符(自减也是相同的)对变量 i 进行操作,那么我们会得到两种情况:'' | ||
+ | 简单的说来:'' | ||
+ | <code cpp> | ||
+ | j = ++i; // i = i +1 then j = i | ||
+ | j = i++ // i_temp = i then i = i +1 then j = i_temp | ||
+ | </ | ||
+ | 也就是说,函数 ++i 的返回值是 '' | ||
+ | <code cpp> | ||
+ | // prefix | ||
+ | int& int:: | ||
+ | { | ||
+ | *this += 1; // i = i + 1 | ||
+ | return *this; | ||
+ | } | ||
+ | |||
+ | //postfix | ||
+ | const int int:: | ||
+ | { | ||
+ | int oldValue = *this; | ||
+ | ++(*this); | ||
+ | return oldValue; | ||
+ | } | ||
+ | |||
+ | </ | ||
+ | 从结果来说: | ||
+ | * **++i 返回的是// | ||
+ | * i++ 返回的是 //i// 的值,返回的是一个**右值**。 | ||
+ | 从程序效率上来说: | ||
+ | * ++i 做了两步:自增,赋值。 | ||
+ | * i++ 做了三步:保存原来的值,自增,用原来的值赋值。 | ||
+ | <WRAP center round info 100%> | ||
+ | **尽量在能用 ++i 的地方都用 ++i**。从上面的信息里可以看出,++i 避免了开辟一块新的临时空间来返回原有 i 的值。在某些复杂迭代器的运行中,++i 的性能提升会显得尤其明显。 | ||
+ | </ | ||
+ | |||
+ | Ref: [[https:// | ||
+ | ==自增运算符和解引用== | ||
+ | Postfix 形式的自增应用于希望使用当前的值后再将其自增的情形。一个典型的例子就是迭代器:比如我们要遍历打印一个 vector,有了 '' | ||
+ | <code cpp> | ||
+ | auto pbeg = v.begin(); | ||
+ | while (pbeg != v.end()) | ||
+ | *pbeg++; // print current value then move to the next position. | ||
+ | </ | ||
+ | 对于 *pbeg++ 来说,自增的优先级高于解应用。所以这个表达式也等价于 '' | ||
+ | - pbeg 留下一份原始拷贝 | ||
+ | - pbeg 自增去了下一个位置 | ||
+ | - 解应用通过留下的拷贝读取了之前 pbeg 所处位置的元素 | ||
+ | ==operand can be evaluated in any order== | ||
+ | 再次提醒,除非operand的运算顺序明显(如上例),我们应该尽量避免用多个运算对同一个数值进行运算/ | ||
+ | <code cpp> | ||
+ | while (beg != s.end() && !isspace(*beg)) | ||
+ | *beg = toupper(*beg++); | ||
+ | </ | ||
+ | '' | ||
+ | <code cpp> | ||
+ | *beg = toupper(*beg) // left first | ||
+ | *(beg+1) = toupper(beg) // right first | ||
+ | </ | ||
+ | 由这样的情形带来的 undefined 操作是一定要注意和避免的。 | ||
+ | ====成员访问运算符==== | ||
+ | 成员访问运算符(// | ||
+ | <code cpp> | ||
+ | string s1 = "a string", | ||
+ | auto n = s1.size(); //member access | ||
+ | n = (*p).size(); | ||
+ | n = p-> | ||
+ | </ | ||
+ | '' | ||
+ | |||
+ | **对于'' | ||
+ | |||
+ | ====条件运算符==== | ||
+ | if-else 语句可以用条件运算符的形式表达出来: | ||
+ | <code cpp > | ||
+ | condition ? expr1(if_true) : expr2(if_false); | ||
+ | </ | ||
+ | * 条件运算符是能保证 order of evaluation 的运算符,其保证条件会被首先求值,并且作为结果选项的表达式**只有一个**会被运行。 | ||
+ | * 如果两个结果表达式都是左值(或者可以转换为左值),那么运算的结果是左值,否则结果是右值。\\ | ||
+ | ===条件运算符的嵌套=== | ||
+ | 条件运算符的结果可以作为另外一个条件运算符的参数(可以是条件也可以是结果表达式)。下例通过两个条件运算的嵌套将输入的成绩分成了 3 个结果段: | ||
+ | <code cpp> | ||
+ | finalgrade = (grade > 90) ? "high pass" | ||
+ | | ||
+ | </ | ||
+ | 需要注意的是,条件运算符遵循右结合律,因此会将 '' | ||
+ | ==条件运算符的输出== | ||
+ | 条件运算符的优先级非常低。在使用 cout 对象输出条件运算符的结果时,需要使用 parenthesis 来强调条件运算符的优先级: | ||
+ | <code cpp> | ||
+ | cout << ((grade > 60) ? " | ||
+ | </ | ||
+ | 否则就会先输出条件判定的结果(0 或者 1),然后使用 cout 对象本身进行条件判断。 | ||
+ | |||
+ | ====位运算符==== | ||
+ | **位运算符**(// | ||
+ | 位运算的 operand 可以是带符号的数,但需要注意是,Left Shift 操作很可能改变符号位(Sign bit)的值。这样的操作是 undefined 的。在值为负数的情况下,位运算符对符号位如何操作是取决于机器的,因此**最好将位运算符用于处理 unsinged 类型的数据。** | ||
+ | \\ \\ | ||
+ | 常见的位运算符如下: | ||
+ | {{ cs: | ||
+ | ===Bitwise Shift Operators=== | ||
+ | 位移运算符(// | ||
+ | <WRAP center round important 100%> | ||
+ | 右边的 operand 必须是**非负整数**,并且必须**小于**结果数据的**位数**。任何造成了超出结果范围的位移都将导致 undefined. | ||
+ | </ | ||
+ | 位移运算符分为左运算符 '' | ||
+ | * **左**运算符:将原有数据往左移动指定的位数,并在右边填充 0 | ||
+ | * **右**运算符: | ||
+ | * 如果数据是 unsigned 类型,将原有数据往右移动指定的位数,并在左边填充 0 | ||
+ | * 如果数据是 signed 类型,插入 0 或是符号位的拷贝视具体的情况而定。 | ||
+ | |||
+ | {{ cs: | ||
+ | ===Bitwise NOT operator=== | ||
+ | 位求反运算符 (//Bitwise NOT operator//) '' | ||
+ | {{ cs: | ||
+ | 上图的数据从 '' | ||
+ | ===AND、OR 和 NOR=== | ||
+ | 位与(bitwise AND)''&'' | ||
+ | {{ cs: | ||
+ | * ''&'' | ||
+ | * '' | ||
+ | * '' | ||
+ | ===位移运算符遵循左结合律=== | ||
+ | 位移运算符遵循左结合律(包括其 stream 重载版本): | ||
+ | <code cpp> | ||
+ | cout << 1 << 2 << endl; // | ||
+ | ((cout << 1) << 2) << endl; | ||
+ | </ | ||
+ | 位移运算符的优先级低于算数运算符,在输入输出 operand 有运算的时候需要用括号保证优先级。 | ||
+ | ====sizeof / 逗号运算符==== | ||
+ | ===sizeof 运算符=== | ||
+ | '' | ||
+ | <code cpp> | ||
+ | sizeof (type) | ||
+ | sizeof expr | ||
+ | </ | ||
+ | 第二种形式下,sizeof 返回的是表达式**返回值所占用的空间**。\\ \\ | ||
+ | 需要提到的是,sizeof 并**不会执行**其 operand: | ||
+ | <code cpp> | ||
+ | Sales_data data, *p; | ||
+ | sizeof(Sales_data); | ||
+ | sizeof data; // | ||
+ | sizeof p; // pointer size | ||
+ | sizeof *p; //size of type to which p points | ||
+ | sizeof data.revenue; | ||
+ | sizeof Sales_data:: | ||
+ | </ | ||
+ | 上面例子中: | ||
+ | * '' | ||
+ | * 新标准下,通过 scope operator 可以在没有对象的情况下直接对成员进行 sizeof 的操作。 | ||
+ | ==sizeof 的结果与类型== | ||
+ | sizeof 返回的结果部分依赖于被处理的类型: | ||
+ | * 对 char / char type expression 的 sizeof 操作一定会返回 '' | ||
+ | * 对 reference 的 sizeof 操作会返回**被引用对象**所占空间大小。 | ||
+ | * 对 pointer 的 sizeof 操作会返回 pointer 所占空间大小。 | ||
+ | * 对 *pointer 的 sizeof 操作会返回**被指针指向对象**所占空间的大小,**无需有效指针**。 | ||
+ | * 对 array 的 sizeof 操作会返回整个 **array** 的大小,该操作中 array 不会转换成指针。 | ||
+ | * 对 string / vector 的 sizeof 操作**不会返回该类型占了多少空间**,只会返回固定的部分大小。 | ||
+ | 因为 sizeof 在 array 上的特性,可以通过 array 的总大小除以 array 元素的大小来得到 array 的长度: | ||
+ | <code cpp> | ||
+ | constexpr size_t sz = sizeof(ia) / sizeof(*ia); | ||
+ | int arr2[sz]; | ||
+ | </ | ||
+ | ===逗号运算符=== | ||
+ | 逗号运算符(// | ||
+ | 逗号运算符的一个常见的应用是在 for 循环中: | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | for(vector< | ||
+ | ivec[ix] = cnt; | ||
+ | } | ||
+ | </ | ||
+ | 只要 '' | ||
+ | ====类型转换==== | ||
+ | 在运算中,如果两种类型可以转换,则称这两种类型是有关联的。基于某些原因,编译器会在程序员没有参与的情况下对参与运算的类型进行转换,这种转换被称为**隐式转换**(// | ||
+ | |||
+ | ==隐式转换的产生场景== | ||
+ | 隐式转换是否发生取决于 operand 的类型。可能发生隐式转换的情况有: | ||
+ | - 小整型的提升:运算中小于 int 的整型会第一时间转化为合适大小的整型。 | ||
+ | - 非 bool 两类型转化为 bool 类型:在条件语句中会进行 non-bool to bool 的转化 | ||
+ | - 初始化 / 赋值中的类型匹配(**右边匹配左边**):初始化中,会将 initializer 的类型转化为对象的类型;赋值中,会将右边 operand 的类型转换为左边 operand 的类型。 | ||
+ | - 算术 / 关系运算中,operands 需要转化为相同类型。 | ||
+ | - 函数调用的类型转换。 | ||
+ | ===算术转换=== | ||
+ | ==算术类型的隐式转换== | ||
+ | 算术类型的转换分为: | ||
+ | * **算术转换**(// | ||
+ | * **整型提升**(// | ||
+ | * 运算转化:为了保证运算 operand 的类型一致而存在的转换。转换的规则取决于是否有 unsigned 类型的参与(见图): | ||
+ | {{ : | ||
+ | |||
+ | 需要说明的是,// | ||
+ | **Perfect Ref: **[[https:// | ||
+ | ==类型转换实例== | ||
+ | <code cpp> | ||
+ | bool flag; char cval; | ||
+ | short sval; unsigned short usval; | ||
+ | int ival; unsigned int uival; | ||
+ | long lval; unsigned long ulval; | ||
+ | float fval; double dval; | ||
+ | 3.14159L + ' | ||
+ | dval + ival; // ival converted to double | ||
+ | dval + fval; // fval converted to double | ||
+ | ival = dval; // dval converted (by truncation) to int | ||
+ | flag = dval; // if dval is 0, then flag is false, otherwise true | ||
+ | cval + fval; // cval promoted to int, then that int converted to float | ||
+ | sval + cval; // sval and cval promoted to int | ||
+ | cval + lval; // cval converted to long | ||
+ | ival + ulval; // ival converted to unsigned long | ||
+ | usval + ival; // promotion depends on the size of unsigned short and int | ||
+ | uival + lval; // conversion depends on the size of unsigned int and long | ||
+ | </ | ||
+ | ===其他类型的隐式转换=== | ||
+ | ==数组对指针的隐式转换== | ||
+ | 在绝大部分应用数组的表达式里,< | ||
+ | * 对数组使用 sizeof | ||
+ | * 对数组使用 decltype | ||
+ | * 对数组使用 typeid | ||
+ | * 使用"&" | ||
+ | * 数组的引用进行初始化 | ||
+ | |||
+ | ==指针的隐式转换== | ||
+ | * const int 0 / nullptr 可以转化成任意指针类型 | ||
+ | * 指向任意 non-const 类型的指针可以转化成 void* | ||
+ | * 指向任意类型的指针可以转化成 const void* | ||
+ | * 在条件语句中,如果指针 / 算术类型的值为 0,则将转换为 false;否则转换为 1 | ||
+ | * pointer / reference to non-const 可以转换为 pointer / reference to const | ||
+ | ==类的转换== | ||
+ | 类的转换通过自定义实现,类的转换只能每次一次。 | ||
+ | <code cpp> | ||
+ | while (cin >> s){} //the result of (cin >> s) is an istream class object and converted to the bool. | ||
+ | </ | ||
+ | ==Examles== | ||
+ | <code cpp> | ||
+ | int ia[10]; // array of ten ints | ||
+ | int* ip = ia; // convert ia to a pointer to the first element | ||
+ | char *cp = get_string(); | ||
+ | if (cp) /* ... */ // true if the pointer cp is not zero | ||
+ | while (*cp) /* ... */ // true if *cp is not the null character | ||
+ | int i; | ||
+ | const int &j = i; // convert a nonconst to a reference to const int | ||
+ | const int *p = &i; // convert address of a nonconst to the address of a const | ||
+ | int &r = j, *q = p; // error: conversion from const to nonconst not allowed | ||
+ | </ | ||
+ | ===显式转换=== | ||
+ | <code cpp> | ||
+ | cast-name< | ||
+ | </ | ||
+ | 其中 type 是被转换的目标类型,expression 是要转换的值。转换**引用**类型将会得到一个**左值**。\\ \\ | ||
+ | 显式转换分为4种类型:'' | ||
+ | ==static_cast== | ||
+ | 任何有明确定义,且不包含 low_level const 的表达式的类型转换,都可以使用 static_cast 来转换,比如: | ||
+ | <code cpp> | ||
+ | //force a floating-point division | ||
+ | double slope = static_cast< | ||
+ | </ | ||
+ | 将较大的算术类型赋值给较小的算术类型的时候,static_cast 非常有用。它明确的告诉了编译器我们得知了将损失精度,但同时也并不在乎的意愿。编译器往往会给出一些 warning 来警告由大至小的类型转换,这时可以将警告关闭。\\ \\ | ||
+ | 除此之外,static_cast 还能用于处理一些编译器不会自动处理的类型转换,比如将指向 '' | ||
+ | <code cpp> | ||
+ | void *p = &d; | ||
+ | double *dp = static_cast< | ||
+ | </ | ||
+ | 进行此类操作的时候需要确保转换后得到的类型就是指针所指的类型,否则结果是 undifined. | ||
+ | ==const_cast== | ||
+ | const_cast 用于将 operand 中的 low_level_const **的限制去掉**: | ||
+ | <code cpp> | ||
+ | const char *pc; | ||
+ | char *p = const_cast< | ||
+ | </ | ||
+ | 需要注意的是,const_cast 只能改变写权限,不能改变被访问对象的类型。比如上面的例子, '' | ||
+ | **使用 const_cast 对常量进行写操作是 Undefined 的**。\\ \\ | ||
+ | 一些例子: | ||
+ | <code cpp> | ||
+ | const char *cp; | ||
+ | char *q = static_cast< | ||
+ | static_cast< | ||
+ | const_cast< | ||
+ | </ | ||
+ | ==reinterpret_cast== | ||
+ | 这是一个不常用的、危险的类型转换。从结果上来说,reinterpret_cast 会导致当前被转换的类型直接被视作为另外一种类型(不通过任何的类型转换)。比如: | ||
+ | <code cpp> | ||
+ | double d = 01101010 00111100 01101010 01000001; | ||
+ | int i = reinterpret_cast< | ||
+ | </ | ||
+ | 经过该转换后,'' | ||
+ | 另外一种方式是通过指针来进行类型的重新诠释: | ||
+ | <code cpp> | ||
+ | int *ip; | ||
+ | char *pc = reinterpret_cast< | ||
+ | </ | ||
+ | '' | ||
+ | 从上面的例子看来,reinterpret_cast 并没有什么作用;但通过 C++ 的标准,reinterpret_cast 实际上被用于这样一种场景:为了使用 C library. 很多 C library 中的函数会接收 '' | ||
+ | Ref: [[https:// | ||
+ | ==Old-style cast== | ||
+ | 早期版本的 C++ 提供两种 cast 的形式: | ||
+ | <code cpp> | ||
+ | type (expr); // | ||
+ | (type) expr; //cstyle | ||
+ | </ | ||
+ | <WRAP center round important 100%> | ||
+ | **避免使用 cast:**\\ \\ | ||
+ | 显示转换会直接干扰正常的类型检测,因此最好避免使用 cast,尤其是 reinterpret_cast。对于其他的 cast 来说, static_cast 与 dynamic_cast 要尽量的少使用;const_cast 的使用除了在函数重载的场景以外,其他都会被考虑为是设计缺陷。\\ \\ | ||
+ | 因此,尽量使用其他方式取代 cast,如果无可避免,设置 scope 来控制被 cast 值的影响范围,并在文档中注明对相关类型的推测。 | ||
+ | </ | ||