C++学习笔记5:表达式

作者: 云中布衣   分类:  学习笔记    热度: (404℃)   时间: 2017-6-26 22:55   标签: #始于C++而不止于C++    

C++提供了丰富的操作符,并定义操作数为内置类型时,这些操作符的含义。除此之外,C++还支持操作符重载,允许程序员自定义类类型时操作符的含义。标准库正是使用这种功能定义用于库类型的操作符。

表达式由一个或多个操作数(operand)通过操作符(operator)组合而成。最简单的表达式(expression)仅包含一个字面值常量或变量。较复杂的表达式则由操作符以及一个或多个操作数构成。每个表达式都会产生一个结果,除特殊用法外,表达式的结果都是右值,可以读取其结果值,但是不允许对它进行赋值。

C++提供了一元操作符(unary operator)和二元操作符(binary operator)两种操作符。作用在一个操作数上的操作符成为一元操作符,如取地址操作符(&)和解引用操作符(*);而二元操作符则作用于两个操作数上,如加法操作符(+)和减法操作符(-)。除此之外,C++还提供了一个使用三个操作数的三元操作符(ternary operator),条件操作符(?:)。需要注意的是有些符号(symbol)即可表示一元操作符也可表示二元操作符。例如,符号*既可以作为(一元)解引用操作符,也可以作为(二元)乘法操作符,这两种用法相互独立、各不相关。对于这类操作符,需要根据该符号所处的上下文来确定它代表一元操作还是二元操作。

1.算术操作符

除特别说明外,下表所示操作符可用于任意算术类型或者任何可转换为算术类型的数据类型。下表按优先级对操作符进行分组,一元操作符优先级最高,其次是乘除操作,接着是二元的加、减法操作。高优先级的操作要比低优先级的结合更加紧密。这些算术操作符都是左结合,这就意味着当操作符的优先级相同时,这些操作符从左向右依次与操作数结合。

算术操作符
操作符
功能
用法
+
一元正号
+ expr
-
一元负号
-  expr
*
乘法
expr1 * expr2
/
除法
expr1 / expr2
%
求余
expr1 % expr2
+
加法
expr1 + expr2
-
减法
expr1 - expr2
使用算术操作符的时候需要注意以下几点:

1)留意操作结果的溢出和其他算术异常,比如除零操作
2)对两个整除做除法,其结果仍为整数
3)求余操作符(%)的操作数只能为整型,当两操作数同号,则结果正负与之相同,若为异号则取决于机器

2.关系操作符和逻辑操作符

关系操作符和逻辑操作符使用算术或指针类型的操作数,并返回bool类型的值。

关系操作符和逻辑操作符
操作符
功能
用法
!
逻辑非
!expr
<
小于
expr1 < expr2
<=
小于等于
expr1 <= expr2
>
大于
expr1 > expr2
>=
大于等于
expr1 >= expr2
==
等于
expr1 == expr2
!=
不等于
expr1 != expr2
&&
逻辑与
expr1 && expr2
||
逻辑或
expr1 || expr2
使用关系操作符和逻辑操作符需要注意以下几点:

1)逻辑与和逻辑或操作符总是先计算其左操作数,然后再计算右操作数。遵从"短路求值"策略
2)不要串连使用关系操作符。例如"if(i<j<k)"应该重写为"if(i < j && j < k)"
3)boo类型可以转换为任何算术类型,bool值false用0表示,而true用1表示

3.位操作符

位操作符使用整型的操作数。位操作符将其整型操作数视为二进制位集合,为每一位提供检验和设置的功能。另外,这类操作符还可用于bitset类型的操作数,该类型具有这里这里所描述的整型操作数的行为。位操作符操纵的整数的类型可以使有符号的也可以是无符号的。如果操作数为负数,则位操作符如何处理其操作数的符号位依赖于机器。于是它们的应用可能不同:在一个应用环境中实现的程序可能无法应用于另一个应用环境。对于位操作符,由于系统不能确保如何处理其操作数的符号位,所以强烈建议使用unsigned整型操作数。

位操作符
操作符
功能
用法
~
按位求反
~expr
<<
按位左移
expr1 << expr2
>>
按位右移
expr1 >> expr2
&
按位求与
expr1 & expr2
^
按位亦或
expr1 ^ expr2
|
按位求或
expr1 | expr2
在实际使用中,标准库提供的bitset操作更直接、更容易阅读和书写、正确使用的可能性更高。而且,bitset对象的大小不受unsigned数的位数限制,通常来说bitset优于整型的低级直接位操作。

前面我们见到过的标准库输入输出是分别重载了位操作符>>和<<。重载的操作符与该操作符的内置类型版本具有相同的优先级和结合性。像其他二元操作符一样,移位操作符也是左结合的,它具有中等优先级,其优先级比算术操作符低,但比关系操作符、赋值操作符和条件操作符优先级高。若IO表达式的操作数包含了比IO操作符优先级低的操作符,相关的优先级别将影响该表达式的书写方式,通常需使用圆括号强制先实现右结合:

cout << 42 + 100; //ok: + has high precedence, so the sum is printed
cout << (10 < 42); //ok:parentheses force intended grouping,prints 1
cout << 10 < 42; //error:attempt to compare cout to 42!
4.赋值操作符

通常,赋值操作将其右操作数的值赋给左操作数。赋值表达式的值使其左操作数的值,其结果的类型为左操作数的类型。然而当左右操作数的类型不同时,该操作实现的类型转换可能会修改被赋的值。赋值操作符的左操作数必须是非const的左值。数组名是不可修改的左值,因此数组不可用作赋值操作的目标,而下标和解引用操作符都返回左值,因此将这两种操作用于非const数组时,其结果可作为赋值操作的左操作数。

同下标和解引用操作一样,赋值操作也返回左值,但与其他二元操作符不同的是,赋值操作具有右结合性。赋值操作具有低优先级,我们可以将赋值操作写在条件表达式中,把赋值操作用作长表达式的一部分,这种做法可缩短程序代码并阐明程序员的意图。有时候我们会对某个对象做某种操作后,再将操作结果重新赋给该对象,这时可以使用复合赋值操作:

+=、-=、*=、/=、%=、<<=、<<=、&=、^=、|=

每个复合赋值操作等价于:

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

5.自增自减操作符

自增(++)和自减(--)操作符为对象加1或减1的操作提供了方便简短的实现方式。他们有前置和后置两种使用形式。前置的形式返回的修改后的值(返回的是对象本身,是左值),而后置操作返回的是未修改的值(是右值)。只有在有必要时才使用后置操作符,因为前置操作需要做的工作较少,只需加1后返回加1后的结果。而后置操作符则必须先保存操作数原来的值,以便返回未加1之前的值作为操作的结果。

简洁即是美:例如在单个表达式组合使用解引用和自增操作可以让程序更加的简洁明了,由于自增操作的优先级高于解引用操作,因此*iter++等效于*(iter++)。下面的程序使用了一种非常通用的C++编程模式输出ivec的内容:

vector<int>::iterator iter = ivec.begin();
//prints 10 9 8 7 6 ...
while(iter != ivec.end()){
   cout <<  *iter++ <<endl; // iterator postfix increment
}
6.箭头操作符

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

item1.same_isbn(item); // run the same_isbn member of item1
如果有一个指向Sales_item对象的指针(或迭代器),则在使用点操作符前,需对该指针(或迭代器)进行解引用:
Sales_item *sp = &item1;
(*sp).same_isbn(item2); // run same_isbn on object to which sp points
这里对sp进行解引用以获得指定的Sales_item对象。然后使用点操作符调用指定对象的same_isbn成员函数。注意要用圆括号把解引用括起来,因为解引用的优先级低于点操作符。在编程时很容易忘记圆括号,因此C++为点操作符后 使用解引用操作定义了一个同义词,箭头操作符(->)。因此上面的程序可以改写如下:
Sales_item *sp = &item1;
sp->same_isbn(item2); // equivalent to (*sp).same_isbn(item2)

7.条件操作符

条件操作符(conditional operator)是C++中唯一的三元操作符,它允许将简单的if-else判断语句嵌入到表达式中。条件操作符语法格式如下,条件为真返回expr1,条件为假返回expr2:

cond ? expr1 : expr2;
条件操作符的优先级相当低,在输出表达中,其优先级要低于标准库的输入输出操作。通常必须用圆括号把条件表达式括起来。

8.sizeof操作符

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

sizeof (typename);
sizeof (expr);
sizeof expr;
将sizeof应用在表达式expr上,将获得该表达式的结果的类型长度。

9.逗号操作符

逗号操作符是一组由逗号分割的表达式,这些表达式从左向右计算。逗号表达式的结果是其最右边表达式的值。如果最右边的操作数是左值,则逗号表达式的值也是左值。

10.符合表达式的求值

含有两个或更多操作符的表达式称为复合表达式(compound expression)。在复合表达式中,操作数和操作符的结合方式决定了整个表达式的值表达式的结果会因为操作符和操作数的分组结合方式的不同而不同。操作数的分组方式取决于操作符的优先级和结合性。也就是说,优先级和结合性决定了表达式的哪个部分用作哪个操作符的操作数。当然如果你不想考虑这些规则,完全可以使用凌驾于优先级之上的圆括号来强制实现某个特殊的分组。

11.new和delete表达式

前面介绍了如何使用new和delete表达式动态创建和释放数组,这两种表达式也可用于动态创建好和释放单个对象。定义变量时,必须指定其数据类型和名字。而创建动态对象时,只需要指定其数据类型,而不必为该对象命名。取而代之的是,new表达式返回指向新创建对象的指针,我们通过该指针来访问此对象:

int i; // named, unintialized int variable
int *pi = new int; // pi points to dynamically allocated, unnamed, unintialized int
这个new表达式在自由存储区中分配创建了一个整型对象,并返回此对象的地址,并用该地址初始化指针pi。

动态创建的对象可用初始化变量的方式实现初始化

int i(1024);
int *pi = new int(1024);
string s(10, '9');
string *ps = new string(10, '9');
如果不提供显式初始化,动态创建的对象与在函数内定义的变量初始化方式相同。对于类类型的对象,用该类的默认构造函数初始化;而内置类型的对象则无初始化。但是我们可以通过在类型名后面使用一对内容为空的圆括号对动态创建的对象做值初始化。对于提供了默认构造函数的类类型来说,没有必要对其对象进行值初始化,因为无论程序是否明确初始化,都会自动调用其默认的构造函数初始化该对象。而对于内置类型或没有定义默认构造函数的类型,采取不同的初始化方式则有显著的差别:
int *pi = new int; // pi points to an unintialized int
int *pi = new int(); // pi points to an int value-initialized to 0
耗尽内存:如果new表达式无法获取需要的内存空间,系统将抛出名为bad_alloc的异常。

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

delete pi ; //pi is dangling pointer
pi = 0; // This operating is safer
const对象的动态分配和回收:下面new表达式返回指向int型const对象的指针。与其他const对象的地址一样,由于new返回的地址上存放的是const对象,因此该地址只能赋给指向const的指针。对于程序员来说,尽管不能改变动态分配后const对象的值,但是可以撤销const对象本身。
const int *pci = new const int(1024);
delete pci; // ok:delete a const object

12.类型转换

在C++中,某些类型之间存在相关的依赖关系。若两种类型相关,则可在需要某种类型的操作数位置上,使用该类型的相关类型对象或值。如果两个类型之间可以相互转换(conversion)则称这两个类型相关。

int ival = 0;
ival = 3.541 + 3; // typically compiles with a warning, ival is int 6. 
类似于上述的转换规则由编译器自动执行,无需程序员介入,我们称之为隐式类型转换(implicit type conversion)。C++定义了算术类型之间的内置转换以尽可能的防止精度损失。

那么合适会发生隐式类型转换呢?

1)在混合类型表达式中默契操作数被转换为相同的类型

2)用作条件表达式被转换为bool类型

3)用一表达式初始化某个变量,或将一表达式赋值给某个变量,则该表达式被转换为该变量的类型,另外在函数调用中也可能发生隐式类型转换,后续再详细介绍。

算术转换:C++语言为内置类型提供了一组转换规则,其中最常用的就是算术转换(arithmetic conversion)。算术转换规则定义了一个类型转换层次,该层次规定了操作数应该按什么层次转换为表达式中最宽的类型。最简单的转换就是整型提升(integral promotion),对于所有比int小的整型,包括char、signed char、unsigned char、short和unsigned short,如果该类型的所有可能值都能包容在int内,它们就会被提升为int型,否则将被提升为unsigned int。

显式转换也称强制类型转换(cast),包括下列名字的而强制类型转换操作符:static_cast、dynamic_cast、const_cast以及reinterpret_cast。虽然有时候确实需要强制类型转换,但他们本质上是非常危险的。通常来说,当我们需要覆盖标准的转换时,就需要显式的使用强制类型转换:

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
命名的强制类型转换符号一般形式如下:
cast-name<type>(expression);
其中cast-name为static_cast、dynamic_cast、const_cast和reinterpret_cast之一,type为转换的类型目标,而expression则是被强制转换的值。

static_cast:编译器隐式执行的任何类型转换都可以由static_cast显式完成。dynamic_cast:支持运行时识别指针或引用所指向的对象。const_cast:故名思义将转换掉表达式的const性质。reinterpret_cast:通常为操作数的位模式提供较低层次的重新解释。其本质上依赖于机器,为了安全的使用reinterpret_cast,要求程序员完全理解所涉及的数据类型以及编译器实现强制类型转换的细节。

建议:避免使用强制类型转换

旧式强制类型转换,在引入命名的强制类型转换操作符之前,显式强制转换用括号将类型括起来实现。

int *ip;
//char *pc = reinterpret_cast<char*>(ip);
char *p = (char*)ip; // equivalent to using reinterpret_cast
旧式强制转换符号有下列两种形式:

type(expr); // Function-style cast notation
(type)expr; // C-language-style cast notation
旧式强制转换依赖于所涉及的数据类型,具有与const_cast、static_cast和reinterpret_cast一样的行为。在合法使用static_cast或const_cast的地方,旧式强制转换提供了与各自对应的命名强制转换一样的功能。如果这两种强制转换均不合法,则旧式强制转换执行reinterpret_cast功能。

int ival;double dval;
ival += int(dval); // static_cast: converts double to int
const char* pc_str;
string_copy((char*)pc_str); // const_cast: casts away const
int *ip
char *pc = (char*)ip; // reinterpret_cast: treats int* as char*
支持旧式强制转换符号,是为了对“在标准C++之前编写的程序”保持向后兼容性,并保持与C语言的兼容性。

(完)

56.8K

发表评论:

© 云中布衣 2015 | Driven by EMLOG  | SiteMap | RunTime: 10.12ms&RSS  | MORE  |   | TOP

文章数量【252】 评论数量【206】 稳定运行【1152天】

Visitor IP Address【54.162.239.233】

Email:ieeflsyu#outlook.com