【C++ Primer每日一刷之十二】 箭头操作符,条件操作符,sizeof操作符,逗号,优先级

5.6. 箭头操作符

C++ 语言为包含点操作符和解引用操作符的表达式提供了一个同义词:箭头操作符(->)。点操作符(第 1.5.2 节)用于获取类类型对象的成员:

item1.same_isbn(item2); // run thesame_isbn member of item1

如果有一个指向 Sales_item 对象的指针(或迭代器),则在使用点操作符

前,需对该指针(或迭代器)进行解引用:

Sales_item *sp = &item1;

(*sp).same_isbn(item2); // run same_isbn onobject to which sp points

这里,对 sp 进行解引用以获得指定的Sales_item 对象。然后使用点操作符调用指定对象的 same_isbn 成员函数。在上述用法中,注意必须用圆括号把解引用括起来,因为解引用的优先级低于点操作符。如果漏掉圆括号,则这段代码的含义就完全不同了:

// run the same_isbn member of sp thendereference the result!

*sp.same_isbn(item2); // error: sp has nomember named same_isbn

这个表达式企图获得 sp 对象的same_isbn 成员。等价于:

*(sp.same_isbn(item2)); // equivalent to*sp.same_isbn(item2);

然而,sp 是一个没有成员的指针;这段代码无法通过编译。

因为编程时很容易忘记圆括号,而且这类代码又经常使用,所以 C++ 为在点操作符后使用的解引用操作定义了一个同义词:箭头操作符(->)。假设有一个指向类类型对象的指针(或迭代器),下面的表达式相互等价:

(*p).foo; // dereference p to get an objectand fetch its member named foo

p->foo; // equivalent way to fetch thefoo from the object to which p points

具体地,可将 same_isbn 的调用重写为:

sp->same_isbn(item2); // equivalent to(*sp).same_isbn(item2)

5.7. 条件操作符

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

cond ? expr1 : expr2;

其中,cond 是一个条件判断表达式(第 1.4.1 节)。条件操作符首先计算cond 的值,如果 cond 的值为 0,则条件为false;如果 cond 非 0,则条件为 true。无论如何,cond 总是要被计算的。然后,条件为 true 时计算 expr1 ,否则计算 expr2 。和逻辑与、逻辑或(&& 和 ||)操作符一样,条件操作符保证了上述操作数的求解次序。expr1 和 expr2 中只有一个表达式被计算。下面的程序说明了条件操作符的用法:

int i = 10, j = 20, k = 30;

// if i > j then maxVal = i else maxVal= j

int maxVal = i > j ? i : j;

避免条件操作符的深度嵌套

可以使用一组嵌套的条件操作符求出三个变量的最大值,并将最大值赋给max:

int max = i > j

? i > k ? i : k

: j > k ? j : k;

理解:

int max = i > j//i大于j,就执行( i > k ? i : k),否则,执行(j > k ? j : k;)

?( i > k ? i : k)

: (j > k ? j : k;)

我们也可以用下面更长却更简单的比较语句实现相同的功能:

int max = i;

if (j > max)

max = j;

if (k > max)

max = k;

在输出表达式中使用条件操作符

条件操作符的优先级相当低。当我们要在一个更大的表达式中嵌入条件表达式时,通常必须用圆括号把条件表达式括起来。例如,经常使用条件操作符根据一定的条件输出一个或另一个值,在输出表达式中,如果不严格使用圆括号将条件操作符括起来,将会得到意外的结果:

cout << (i < j ? i : j); // ok:prints larger of i and j

cout << (i < j) ? i : j; // prints1 or 0!

cout << i < j ? i : j; // error:compares cout to int

第二个表达式比较有趣:它将i 和j 的比较结果视为 << 操作符的操作数,输出 1 或 0。 << 操作符返回cout 值,然后将返回结果作为条件操作符的判断条件。也就是,第二个表达式等效于:

cout << (i < j); // prints 1 or 0

cout ? i : j; // test cout and thenevaluate i or j

// depending on whether cout evaluates totrue or

false

5.8. sizeof 操作符

sizeof 操作符的作用是返回一个对象或类型名的长度,返回值的类型为size_t(第 3.5.2 节),长度的单位是字节(第 2.1 节)。size_t 表达式的结果是编译时常量,该操作符有以下三种语法形式:

sizeof (type name);

sizeof (expr);

sizeof expr;

将 sizeof 应用在表达式expr 上,将获得该表达式的结果的类型长度:

Sales_item item, *p;

// three ways to obtain size required tohold an object of type Sales_item

sizeof(Sales_item); // size required tohold an object of type Sales_item

sizeof item; // size of item‘s type, e.g.,sizeof(Sales_item)

sizeof *p; // size of type to which ppoints, e.g., sizeof(Sales_item)

将 sizeof 用于 expr 时,并没有计算表达式 expr 的值。特别是在 sizeof*p 中,指针 p 可以持有一个无效地址,因为不需要对 p 做解引用操作。

使用 sizeof 的结果部分地依赖所涉及的类型:

? 对char 类型或值为 char 类型的表达式做 sizeof 操作保证得 1。

? 对引用类型做 sizeof 操作将返回存放此引用类型对象所需的内在空间

大小。

? 对指针做 sizeof 操作将返回存放指针所需的内在大小;注意,如果要获

取该指针所指向对象的大小,则必须对指针进行引用。

? 对数组做 sizeof 操作等效于将对其元素类型做 sizeof 操作的结果乘上数组元素的个数。

因为 sizeof 返回整个数组在内存中的存储长度,所以用 sizeof 数组的结果除以 sizeof 其元素类型的结果,即可求出数组元素的个数:

// sizeof(ia)/sizeof(*ia) returns thenumber of elements in ia

int sz = sizeof(ia)/sizeof(*ia);

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;

上述的 for 语句在循环表达式中使ix 自增 1 而 cnt 自减 1。每次循环均要修改 ix 和 cnt的值。当检验 ix 的条件判断成立时,程序将下一个元素重新设置为 cnt 的当前值。

5.10. 复合表达式的求值

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

操作数的分组结合方式取决于操作符的优先级和结合性。也就是说,优先级和结合性决定了表达式的哪个部分用作哪个操作符的操作数。如果程序员不想考虑这些规则,可以在复合表达式中使用圆括号强制实现某个特殊的分组。

优先级规定的是操作数的结合方式,但并没有说明操作数的计算顺序。在大多数情况下,操作数一般以最方便的次序求解。

5.10.1. 优先级

表达式的值取决于其子表达式如何分组。例如,下面的表达式,如果纯粹从

左向右计算,结果为 20:

6 + 3 * 4 / 2 + 2;

想像中其他可能的结果包括 9、14 和 36。在 C++ 中,该表达式的值应为14。

乘法和除法的优先级高于加法操作,于是它们的操作数先于加法操作的操作数计算。但乘法和除法的优先级相同。当操作符的优先级相同时,由其结合性决定求解次序。算术操作具有左结合性,这意味着它们从左向右结合。因此上面表达式等效于:

int temp = 3 * 4; // 12

int temp2 = temp / 2; // 6

int temp3 = temp2 + 6; // 12

int result = temp3 + 2; // 14

圆括号凌驾于优先级之上

我们可使用圆括号推翻优先级的限制。使用圆括号的表达式将用圆括号括起来的子表达式视为独立单元先计算,其他部分则以普通的优先级规则处理。例如,下面的程序在前述表达式上添加圆括号,强行更改其操作次序,可能得到四种结果:

// parentheses on this expression matchdefault

precedence/associativity

cout << ((6 + ((3 * 4) / 2)) + 2)<< endl; // prints 14

// parentheses result in alternativegroupings

cout << (6 + 3) * (4 / 2 + 2)<< endl; // prints 36

cout << ((6 + 3) * 4) / 2 + 2<< endl; // prints 20

cout << 6 + 3 * 4 / (2 + 2) <<endl; // prints 9

我们已经通过前面的例子了解了优先级规则如何影响程序的正确性。例如,考虑第 5.5 节第二个建议框中描述的表达式:

*iter++;

其中,++的优先级高于*操作符,这就意味着 iter++ 先结合。而操作符 *

的操作数是 iter 做了自增操作后的结果。如果我们希望对 iter 所指向的值做

自增操作,则必须使用圆括号强制实现我们的目的:

(*iter)++; // increment value to which iterrefers and yield

unincremented value

圆括号指明操作符 * 的操作数是iter,然后表达式以 *iter 作为 ++操作

符的操作数。

另一个例子,回顾一下第 5.4.2 节中的 while 循环条件:

while ((i = get_value()) != 42) {

赋值操作上的圆括号是必需的,这样才能实现预期的操作:将 get_value 的

返回值赋给 i,然后检查刚才赋值的结果是否为42。如果赋值操作上没有加圆

括号,结果将是先判断 get_value 的返回值是否为 42,然后将判断结果 true

或 false 值赋给 i,这意味着 i 的值只能是 1 或 0。

5.10.2. 结合性

结合性规定了具有相同优先级的操作符如何分组。我们已经遇到过涉及结合性的例子。其中之一使用了赋值操作的右结合性,这个特性允许将多个赋值操作串接起来:

ival = jval = kval = lval // rightassociative

(ival = (jval = (kval = lval))) //equivalent, parenthesized version

该表达式首先将 lval 赋给kval ,然后将 kval 的值赋给 jval ,最后将jval 的值再赋给 ival。

另一方面,算术操作符为左结合。表达式

ival * jval / kval * lval // leftassociative

(((ival * jval) / kval) * lval) //equivalent, parenthesized version

先对 ival 和 jval 做乘法操作,然后乘积除以 kval,最后再将其商与

lval 相乘。

表 5.4 按照优先级顺序列出了C++ 的全部操作符。该表以双横线分割成不

同的段,每段内各个操作符的优先级相同,且都高于后面各段中的操作符。例如,

前自增操作符和解引用操作符的优先级相同,它们的优先级都比算术操作符或关

系操作符高。此表中大部分操作符已经介绍过,而少数未介绍的操作符将在后续

章节中学习。

表 5.4. 操作符的优先级

Associativity

and Operator

操作符及其结合性

Function

功能

Use

用法

L :: global scope(全局作用域) :: name

L :: class scope(类作用域) class :: name

L :: namespace scope(名字空

间作用域)

namespace :: name

L . member selectors(成员选

择)

object . member

L -> member selectors(成员选

择)

pointer -> member

Associativity

and Operator

【操作符及其结合性】【功能】【用法】

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 +=, -=,

R <<=, >>=,

R &=,|=, ^=

R throw throw exception(抛出异常)

throw expr

L , comma(逗号) expr, expr

5.10.3. 求值顺序

在第 5.2 节中,我们讨论了&& 和 || 操作符计算其操作数的次序:当且仅当其右操作数确实影响了整个表达式的值时,才计算这两个操作符的右操作数。根据这个原则,可编写如下代码:

// iter only dereferenced if it isn‘t atend

while (iter != vec.end() && *iter!= some_val)

C++中,规定了操作数计算顺序的操作符还有条件(?:)和逗号操作符。除此之外,其他操作符并未指定其操作数的求值顺序。例如,表达式

f1() * f2();

在做乘法操作之前,必须调用 f1 函数和 f2 函数,毕竟其调用结果要相乘。

然而,我们却无法知道到底是先调用 f1 还是先调用 f2。

其实,以什么次序求解操作数通常没有多大关系。只有当操作符的两个操作数涉及到同一个对象,并改变其值时,操作数的计算次序才会影响结果。

如果一个子表达式修改了另一个子表达式的操作数,则操作数的求解次序就变得相当重要:

// oops! language does not define order ofevaluation

if (ia[index++] < ia[index])

此表达式的行为没有明确定义。问题在于:< 操作符的左右操作数都使用了index 变量,但是,左操作数更改了该变量的值。假设 index 初值为 0,编译器可以用下面两种方式之一求该表达式的值:

if (ia[0] < ia[0]) // execution if rhsis evaluated first

if (ia[0] < ia[1]) // execution if lhsis evaluated first

可以假设程序员希望先求左操作数的值,因此 index 的值加 1。如果是这样的话,比较 ia[0] 和 ia[1] 的值。然而,C++ 语言不能确保从左到右的计算次序。事实上,这类表达式的行为没有明确定义。一种实现可能是先计算右操作数,于是 ia[0] 与自己做比较,要不然就是做完全不同的操作。

建议:复合表达式的处理

初学 C 和 C++ 的程序员一般很难理解求值顺序、优先级和结合性规则。误解表达式和操作数如何求解将导致大量的程序错误。此外,除非程序员已经完全理解了相关规则,否则这类错误很难发现,因为仅靠阅读程序是无法排除这些错误的。

下面两个指导原则有助于处理复合表达式:

1. 如果有怀疑,则在表达式上按程序逻辑要求使用圆括号强制操作数的组合。

2. 如果要修改操作数的值,则不要在同一个语句的其他地方使用该操作数。如果必须使用改变的值,则把该表达式分割成两个独立语句:在一个语句中改变该操作数的值,再在下一个语句使用它。第二个规则有一个重要的例外:如果一个子表达式修改操作数的值,然后将该子表达式的结果用于另一个子表达式,这样则是安全的。例如,*++iter 表达式的自增操作修改了 iter 的值,然后将 iter(修改后)的值用作 * 操作符的操作数。对于这个表达式或其他类似的表达式,其操作数的计算次序无关紧要。而为了计算更复杂的表达式,改变操作数值的子表达式必须首先计算。这种方法很常用,不会产生什么问题。

一个表达式里,不要在两个或更多的子表达式中对同一对象做自增或自减操作。以一种安全而且独立于机器的方式重写上述比较两个数组元素的程序:

if (ia[index] < ia[index + 1])

{

// do whatever

}

++index;

现在,两个操作数的值不会相互影响。

时间: 2024-10-03 16:15:20

【C++ Primer每日一刷之十二】 箭头操作符,条件操作符,sizeof操作符,逗号,优先级的相关文章

【C++ Primer每日一刷之十】 操作符(一)

表达式 C++ 提供了丰富的操作符,并定义操作数为内置类型时,这些操作符的含义.除此之外,C++ 还支持操作符重载,允许程序员自定义用于类类型时操作符的含义.标准库正是使用这种功能定义用于库类型的操作符. 本章重点介绍 C++ 语言定义的操作符,它们使用内置类型的操作数:本章还会介绍一些标准库定义的操作符.第十四章将学习如何定义自己的重载操作符. 表达式由一个或多个操作数通过操作符组合而成.最简单的表达式仅包含一个字面值常量或变量.较复杂的表达式则由操作符以及一个或多个操作数构成. 每个表达式都

C primer plus 第五版十二章习题

看完C prime plus(第五版)第十二章,随带完成了后面的习题. 1.不使用全局变量,重写程序清单12.4的程序. 先贴出12.4的程序,方便对照: 1 /* global.c --- 使用外部变量 */ 2 #include <stdio.h> 3 int units = 0; //一个外部变量 4 void critic(void); 5 int main(void) 6 { 7 extern int units; 8 9 printf ("How many pounds

【C++ Primer每日一刷之九】创建动态数组

表达式 C++ 提供了丰富的操作符,并定义操作数为内置类型时,这些操作符的含义.除此之外,C++ 还支持操作符重载,允许程序员自定义用于类类型时操作符的含义.标准库正是使用这种功能定义用于库类型的操作符. 本章重点介绍 C++ 语言定义的操作符,它们使用内置类型的操作数:本章还会介绍一些标准库定义的操作符.第十四章将学习如何定义自己的重载操作符. 表达式由一个或多个操作数通过操作符组合而成.最简单的表达式仅包含一个字面值常量或变量.较复杂的表达式则由操作符以及一个或多个操作数构成. 每个表达式都

【iOS开发每日小笔记(十二)】仿Facebook登录界面 错误提示抖动 利用CAAnimation设置动画效果

这篇文章是我的[iOS开发每日小笔记]系列中的一片,记录的是今天在开发工作中遇到的,可以用很短的文章或很小的demo演示解释出来的小心得小技巧.它们可能会给用户体验.代码效率得到一些提升,或是之前自己没有接触过的技术,很开心的学到了,放在这里得瑟一下.90%的作用是帮助自己回顾.记忆.复习. 原本以为国庆假期可以有时间看看书,写写博客.实际上大部分时间都被赶场参加婚礼和到处去亲戚家串门吃饭所占用.眼看明天还剩最后一天时间,今天赶紧来更新一篇,也算是没有完全荒废这7天长假吧! Facebook的客

学习C++ Primer 的个人理解(十二)

动态内存与智能指针 在C++中, 动态内存用 new来分配空间并返回一个指向该对象的指针 用delete来销毁. 由于手动的对动态内存进行操作容易出现问题.所以新的标准库提供了两种智能指针. 智能指针的行为类似于常规指针.区别在于他负责自动释放所指对象. shared_ptr : 允许多个指针指向同一个对象. unique_ptr :独占所指向的对象 weak_ptr   :是一种弱引用,指向shared_ptr 所管理的对象 shared_ptr 类 类似vector ,shared_ptr

C primer plus 读书笔记第十二章

C的强大功能之一在于它允许我们控制程序的细节.C的内存管理系统正是这种控制能力的例子.它通过让我们决定哪些函数知道哪些变量以及一个变量在程序中存在多长时间来实现这些控制. 1.存储类及其说明符 主要的定义:作用域.链接以及存储时间.其他编程语言也有类似的概念.C语言通过这三个概念定义了5中存储类.其说明符分别为auto.register.static.extern和typedef. 2.存储类和函数 函数也分为外部的和静态的.关键字是extern和static.默认情况下是外部的. 3.mall

【C++ Primer每日一刷之八】之八 C 风格字符串

4.3 C 风格字符串 尽管 C++ 支持 C 风格字符串,但不应该在 C++ 程序中使用这个类型.C 风格字符串常常带来许多错误,是导致大量安全问题的根源. 在前面我们第一次使用了字符串字面值,并了解字符串字面值的类型是字符常量的数组,现在可以更明确地认识到:字符串字面值的类型就是const char 类型的数组.C++ 从 C 语言继承下来的一种通用结构是C 风格字符串,而字符串字面值就是该类型的实例.实际上,C 风格字符串既不能确切地归结为 C 语言的类型,也不能归结为 C++ 语言的类型

【C++ Primer每日一刷之七】指针操作

4.2.3 指针操作 指针提供间接操纵其所指对象的功能.与对迭代器进行解引用操作一样,对指针进行解引用可访问它所指的对象,* 操作符(解引用操作符)将获取指针所指的对象: string s("hello world"); string *sp = &s; // sp holds theaddress of s cout <<*sp; // prints hello world 对 sp 进行解引用将获得 s 的值,然后用输出操作符输出该值,于是最后一条语句输出了 s

【C++ Primer每日一刷之八】之九 创建动态数组

4.3.1. 创建动态数组 数组类型的变量有三个重要的限制:[数组长度固定不变],[在编译时必须知道其长度],[数组只在定义它的块语句内存在]. 实际的程序往往不能忍受这样的限制--它们需要在运行时动态地分配数组. 虽然数组长度是固定的,但动态分配的数组不必在编译时知道其长度,可以(通常也是)在运行时才确定数组长度.与数组变量不同,动态分配的数组将一直存在,直到程序显式释放它为止.每一个程序在执行时都占用一块可用的内存空间,用于存放动态分配的对象,此内存空间称为程序的自由存储区或堆. C 语言程