C++primer 读书笔记 Chapter 5 some tips about expressions

1.算术操作符:

一元操作符优先级最高,其次是乘、除操作,接着是二元的加、减法操作。高优先级的操作符要比低优先级的结合得更紧密。这些算术操作符都是左结合,这就意味着当操作符的优先级相同时,这些操作符从左向右依次与操作数结合。

Operator

操作符

Function

功能

Use

用法

+

unary plus(一元正号)

+ expr

-

unary minus(一元负号)

- expr

*

multiplication(乘法)

expr * expr

/

division(除法)

expr / expr

%

remainder(求余)

expr % expr

+

addition(加法)

expr + expr

-

subtraction(减法)

expr - expr


这里要注意的是:对两个整数做除法,结果仍为整数,如果它的商包含小数部分,则小数部分会被截除:

     double  dval1 = 21/6;  //  integral result obtained by truncating the remainder
     double dval2 = 21/7;  //  no remainder, result is an integral value
输出 dval1dval2时,结果都为3

 

操作符 % 称为“求余(remainder)”或“求模(modulus)”操作符,用于计算左操作数除以右操作数的余数。该操作符的操作数只能为整型,包括 boolcharshort int long 类型,以及对应的 unsigned 类型。

 

 如果两个操作数为正,除法(/)和求模(%)操作的结果也是正数(或零);如果两个操作数都是负数,除法操作的结果为正数(或零),而求模操作的结果则为负数(或零);如果只有一个操作数为负数,这两种操作的结果取决于机器;求模结果的符号也取决于机器,而除法操作的值则是负数(或零)。

 

 当只有一个操作数为负数时,求模操作结果值的符号可依据分子(被除数)或分母(除数)的符号而定。如果求模的结果随分子的符号,则除出来的值向零一侧取整;如果求模与分母的符号匹配,则除出来的值向负无穷一侧取整。

 

2.关系操作符:

Each of these operators yields bool

下列操作符都产生 bool

Operator

操作符

Function

功能

Use

用法

!

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

 

注意:不应该串接使用关系操作符

关系操作符(<<=><=)具有左结合特性。事实上,由于关系操作符返回bool类型的结果,因此很少使用其左结合特性。如果把多个关系操作符串接起来使用,结果往往出乎预料:

       if(i<j<k)  {/*....*/}

这种写法只要 k 大于 1,上述表达式的值就为 true。这是因为第二个小于操作符的左操作数是第一个小于操作符的结果:true false。也就是,该条件将 k 与整数 0 1 做比较。为了实现我们想要的条件检验,应重写上述表达式如下:

       if (i < j && j < k) { /* ... */ }

3.位操作符:

位操作符使用整型的操作数。位操作符将其整型操作数视为二进制位的集合,为每一位提供检验和设置的功能。另外,这类操作符还可用于 bitset 类型的操作数,该类型具有这里所描述的整型操作数的行为。

 

Operator

操作符

Function

功能

Use

用法

~

bitwise NOT(位求反)

~expr

<< 

left shift(左移)

expr1 << expr2

>> 

right shift(右移)

expr1 >> expr2

&

bitwise AND(位与)

expr1 & expr2

^

bitwise XOR(位异或)

expr1 ^ expr2

|

bitwise OR(位或)

expr1 | expr2

位操作符操纵的整数的类型可以是有符号的也可以是无符号的。如果操作数为负数,则位操作符如何处理其操作数的符号位依赖于机器。于是它们的应用可能不同:在一个应用环境中实现的程序可能无法用于另一应用环境。

<< >> 操作符提供移位操作,其右操作数标志要移动的位数。这两种操作符将其左操作数的各个位向左(<<)或向右(>>)移动若干个位(移动的位数由其右操作数指定),从而产生新的值,并丢弃移出去的位。

unsigned char bits = 1;                            10011011

 bits << 1; // left shift                              00110110

 bits << 2; // left shift                              01101100

 bits >> 3; // right shift                           00010011

左移操作符(<<)在右边插入 0 以补充空位。对于右移操作符(>>),如果其操作数是无符号数,则从左边开始插入 0;如果操作数是有符号数,则插入符号位的副本或者 0 值,如何选择需依据具体的实现而定。移位操作的右操作数不可以是负数,而且必须是严格小于左操作数位数的值。否则,操作的效果未定义。

位异或(互斥或,exclusive or)操作符(^)也需要两个整型操作数。在每个位的位置,如果两个操作数对应的位只有一个(不是两个)为 1,则操作结果中该位为 1,否则为 0

位或(包含或,inclusive or)操作符(|)需要两个整型操作数。在每个位的位置,如果两个操作数对应的位有一个或者两个都为 1,则操作结果中该位为 1,否则为 0

4IO操作符:

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

移位操作符具有中等优先级:其优先级比算术操作符低,但比关系操作符、赋值操作符和条件操作符优先级高。

5.赋值操作符:

赋值操作符的左操作数必须是非 const 的左值,具有右结合特性。

6 . 复合操作符:

a op= b;

a = a op b;

这两种语法形式存在一个显著的差别:使用复合赋值操作时,左操作数只计算了一次;而使用相似的长表达式时,该操作数则计算了两次,第一次作为右操作数,而第二次则用做左操作数。除非考虑可能的性能价值,在很多(可能是大部分的)上下文环境里这个差别不是本质性的。

7 . 自加自减操作符:

前置操作返回加1后的值,所以返回对象本身,这是左值。而后置操作返回的则是右值。

为什么前置操作符返回的是左值,后置操作符返回的是右值呢?网上有大哥如下解释:

左值意味着这个值是内存里一个可访问的地址,右值表示一个数据(可能没有确切的地址)

比如说 ++i 先将 i 所存放的数加一,再返回i,&(++i)是有意义的,可以作为一个左值,

i++ i的值加一,并且返回没有增加前的i,这个时候这个(i++)是一个数字,没有固定的存放地址,所以只能作为右值了.

另外附上一篇参考博文: http://blog.csdn.net/mynote/archive/2010/01/26/5257165.aspx帮助大家理解。

书上提醒我们:

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

8.条件操作符:

注意下面语句:

 (cout << (i < j)) ? i : j;

输出的是10,为什么呢?
首先输出i<j的值,即为1(或0,然后表式cout << (i < j)返回cout的值,1(或0
然后变为cout ? i : j,或者说变为 1(或0 ? i: j;它返回i的值,但是这个表达式的值没被输出,程序没有使用它。因此不输出i的值。

9sizeof操作符

sizeof 操作符的作用是返回一个对象或类型名的长度,返回值的类型为 size_t,长度的单位是字节。

注意的是:将 sizeof 用于 expr 时,并没有计算表达式 expr 的值。特别是在 sizeof *p 中,指针 p 可以持有一个无效地址(未初始化,指向随机地址),因为不需要对 p 做解引用操作。对指针做 sizeof 操作将返回存放指针所需的内在大小;注意,如果要获取该指针所指向对象的大小,则必须对指针进行引用。、

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

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

10.优先级

 

5.4. 操作符的优先级

Associativity
and Operator

操作符及其结合性

Function

功能

Use

用法

See
Page

参见页码

L

::

global scope(全局作用域)

:: name

p. 450

L

::

class scope(类作用域)

class :: name

p. 85

L

::

namespace scope(名字空间作用域)

namespace :: name

p. 78

L

.

member selectors(成员选择)

object . member

p. 25

L

->

member selectors(成员选择)

pointer -> member

p. 164

L

[]

subscript(下标)

variable [ expr ]

p. 113

L

()

function call(函数调用)

name (expr_list)

p. 25

L

()

type construction(类型构造)

type (expr_list)

p. 460

R

++

postfix increment(后自增操作)

lvalue++

p. 162

R

--

postfix decrement(后自减操作)

lvalue--

p. 162

R

typeid

type ID(类型 ID

typeid (type)

p. 775

R

typeid

run-time type ID(运行时类型 ID

typeid (expr)

p. 775

R

explicit cast(显式强制类型转换)

type conversion(类型转换)

cast_name <type>(expr)

p. 183

R

sizeof

size of object(对象的大小)

sizeof expr

p. 167

R

sizeof

size of type(类型的大小)

sizeof(type)

p. 167

R

++

prefix increment(前自增操作)

++ lvalue

p. 162

R

--

prefix decrement(前自减操作)

-- lvalue

p. 162

R

~

bitwise NOT(位求反)

~expr

p. 154

R

!

logical NOT(逻辑非)

!expr

p. 152

R

-

unary minus(一元负号)

-expr

p. 150

R

+

unary plus(一元正号)

+expr

p. 150

R

*

dereference(解引用)

*expr

p. 119

R

&

address-of(取地址)

&expr

p. 115

R

()

type conversion(类型转换)

(type) expr

p. 186

R

new

allocate object(创建对象)

new type

p. 174

R

delete

deallocate object(释放对象)

delete expr

p. 176

R

delete[]

deallocate array(释放数组)

delete[] expr

p. 137

L

->*

ptr to member select(指向成员操作的指针)

ptr ->* ptr_to_member

p. 783

L

.*

ptr to member select(指向成员操作的指针)

obj .*ptr_to_member

p. 783

L

*

multiply(乘法)

expr * expr

p. 149

L

/

divide(除法)

expr / expr

p. 149

L

%

modulo (remainder)(求模(求余))

expr % expr

p. 149

L

+

add(加法)

expr + expr

p. 149

L

-

subtract(减法)

expr - expr

p. 149

L

<< 

bitwise shift left(位左移)

expr << expr

p. 154

L

>> 

bitwise shift right(位右移)

expr >> expr

p. 154

L

< 

less than(小于)

expr < expr

p. 152

L

<=

less than or equal(小于或等于)

expr <= expr

p. 152

L

> 

greater than(大于)

expr > expr

p. 152

L

>=

greater than or equal(大于或等于)

expr >= expr

p. 152

L

==

equality(相等)

expr == expr

p. 152

L

!=

inequality(不等)

expr != expr

p. 152

L

&

bitwise AND(位与)

expr & expr

p. 154

L

^

bitwise XOR()

expr ^ expr

p. 154

L

|

bitwise OR(位异或)

expr | expr

p. 154

L

&&

logical AND(逻辑与)

expr && expr

p. 152

L

||

logical OR(逻辑或)

expr || expr

p. 152

R

?:

conditional(条件操作)

expr ? expr : expr

p. 165

R

=

assignment(赋值操作)

lvalue = expr

p. 159

R

*=, /=, %=,

compound assign(复合赋值操作)

lvalue += expr, etc.

p. 159

R

+=, -=,

 

 

p. 159

R

<<=, >>=,

 

 

p. 159

R

&=,|=, ^=

 

 

p. 159

R

throw

throw exception(抛出异常)

throw expr

p. 216

L

,

comma(逗号)

expr , expr

p. 168

 

11.再谈newdelete

动态创建对象时,只需指定其数据类型,而不必为该对象命名。取而代之的是,new 表达式返回指向新创建对象的指针,我们通过该指针来访问此对象:

int *pi = new int;
动态创建的对象可用初始化变量的方式实现初始化:

 int *pi = new int(1024);  // object to which pi points is 1024

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

同样也可对动态创建的对象做值初始化(value-initialize):

     string *ps = new string();  // initialized to empty string
     int *pi = new int();  // pi points to an int value-initialized to 0
     cls *pc = new cls();  // pc points to a value-initialized object of type cls
值初始化的 () 语法必须置于类型名后面,而不是变量后。

     int x(); // does not value initialize x
这个语句声明了一个名为 x、没有参数而且返回 int 值的函数。

如果指针指向不是用 new 分配的内存地址,则在该指针上使用 delete 是不合法的。

如果指针的值为 0,则在其上做 delete 操作是合法的,但这样做没有任何意义:

      int *ip = 0;
     delete ip; // ok: always ok to delete a pointer that is equal to 0
C++ 保证:删除 0 值的指针是安全的。

这里有一点要注意:

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

       delete pi;
该命令释放 pi 指向的 int 型对象所占用的内存空间。

执行语句

       delete p;
后,p 变成没有定义。在很多机器上,尽管 p 没有定义,但仍然存放了它之前所指向对象的地址,然而 p 所指向的内存已经被释放,因此 p 不再有效。

 

删除指针后,该指针变成悬垂指针。悬垂指针指向曾经存放对象的内存,但该对象已经不再存在了。悬垂指针往往导致程序错误,而且很难检测出来。

 

12.类型转换:

隐式类型转换:包括算术转换,赋值转换,输出转换等

在下列情况下,将发生隐式类型转换:

·         在混合类型的表达式中,其操作数被转换为相同的类型:      int ival;
                                                                                 double dval;
                                                                                 ival >= dval // ival converted to double

·         用作条件的表达式被转换为 bool 类型:                           int ival;
                                                                                 if (ival)   // ival converted to bool
                                                                                 while (cin) // cin converted to bool

条件操作符(?:)中的第一个操作数以及逻辑非(!)、逻辑与(&&)和逻辑或(||)的操作数都是条件表达式。出现在 ifwhilefor do while 语句中的同样也是条件表达式。

  • 用一表达式初始化某个变量,或将一表达式赋值给某个变量,则该表达式被转换为该变量的类型:

                                                                                            int ival=3.14; //3.14converted to int
                                                                                            int *ip;
                                                                                            ip = 0; // the int 0 converted to a null pointer of type int *

 (1)算术转换:

算术转换规则定义了一个类型转换层次,该层次规定了操作数应按什么次序转换为表达式中最宽的类型。在包含多种类型的表达式中,转换规则要确保计算值的精度。例如,如果一个操作数的类型是 long double,则无论另一个操作数是什么类型,都将被转换为 long double

最简单的转换为整型提升:对于所有比 int 小的整型,包括 charsigned charunsigned charshort unsigned short,如果该类型的所有可能的值都能包容在 int 内,它们就会被提升为 int 型,否则,它们将被提升为 unsigned int。如果将 bool 值提升为 int ,则 false 转换为 0,而 true 则转换为 1

 

若表达式中使用了无符号( unsigned )数值,所定义的转换规则需保护操作数的精度。unsigned 操作数的转换依赖于机器中整型的相对大小,因此,这类转换本质上依赖于机器。

 

(2)指针转换:

在使用数组时,大多数情况下数组都会自动转换为指向第一个元素的指针:

     int ia[10];    // array of 10 ints
     int* ip = ia;  // convert ia to pointer to first element
不将数组转换为指针的例外情况有:数组用作取地址(&)操作符的操作数或 sizeof 操作符的操作数时,或用数组对数组的引用进行初始化时,不会将数组转换为指针。

 

显式转换(强制转换):

因为要覆盖通常的标准转换,所以需显式使用强制类型转换。面的复合赋值:

double dval;

     int ival;

     ival *= dval; // ival = ival * dval

为了与 dval 做乘法操作,需将 ival 转换为 double 型,然后将乘法操作的 double 型结果截尾为 int 型,再赋值给 ival。为了去掉将 ival 转换为 double 型这个不必要的转换,可通过如下强制将 dval 转换为 int 型:

ival *= static_cast<int>(dval); // converts dval to int

 

强制转换分为以下几种:

dynamic_cast 支持运行时识别指针或引用所指向的对象。

 

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

     const char *pc_str;

     char *pc = string_copy(const_cast<char*>(pc_str));

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

编译器隐式执行的任何类型转换都可以由 static_cast 显式完成:

double d = 97.0;

     // cast specified to indicate that the conversion is intentional

     char ch = static_cast<char>(d);

 

reinterpret_cast 通常为操作数的位模式提供较低层次的重新解释。

例如,对于下面的强制转换:

 int *ip;

     char *pc = reinterpret_cast<char*>(ip);

程序员必须永远记得 pc 所指向的真实对象其实是 int 型,而并非字符数组。任何假设 pc 是普通字符指针的应用,都有可能带来有趣的运行时错误。例如,下面语句用 pc 来初始化一个 string 对象:

    string str(pc);

它可能会引起运行时的怪异行为。

pc 初始化 str 这个例子很好地说明了显式强制转换是多么的危险。问题源于类型已经改变时编译器没有提供任何警告或错误提示。当我们用 int 型地址初始化 pc 时,由于显式地声明了这样的转换是正确的,因此编译器不提供任何错误或警告信息。后面对 pc 的使用都假设它存放的是 char* 型对象的地址,编译器确实无法知道 pc 实际上是指向 int 型对象的指针。因此用 pc 初始化 str 是完全正确的——虽然实际上是无意义的或是错误的。查找这类问题的原因相当困难,特别是如果 ip pc 的强制转换和使用 pc 初始化 string 对象这两个应用发生在不同文件中的时候。(不理解)

网上找到的资料,不是很懂:

static_cast  reinterpret_cast   

      reinterpret_cast是为了映射到一个完全不同类型的意思,这个关键词在我们需要把类型映射回原有类型时用到它。我们映射到的类型仅仅是为了故弄玄虚和其他目的,这是所有映射中最危险的。(这句话是C++编程思想中的原话)

static_cast reinterpret_cast 操作符修改了操作数类型。它们不是互逆的; static_cast 在编译时使用类型信息执行转换,在转换执行必要的检测(诸如指针越界计算, 类型检查). 其操作数相对是安全的。另一方面;reinterpret_cast 仅仅是重新解释了给出的对象的比特模型而没有进行二进制转换, 例子如下:

int n=9; double d=static_cast < double > (n);

上面的例子中, 我们将一个变量从 int 转换到 double。这些类型的二进制表达式是不同的。 要将整数 9 转换到 双精度整数 9static_cast 需要正确地为双精度整数 d 补足比特位。其结果为 9.0。而reinterpret_cast 的行为却不同:

int n=9;

double d=reinterpret_cast<double & > (n);

这次, 结果有所不同. 在进行计算以后, d 包含无用值. 这是因为 reinterpret_cast 仅仅是复制 n 的比特位到 d, 没有进行必要的分析.

因此, 你需要谨慎使用 reinterpret_cast.

 

 

 

你可能感兴趣的:(C++primer 读书笔记 Chapter 5 some tips about expressions)