【C++ primer】第4章 表达式


Part I: The Basics
Chapter 4. Expressions

表达式


4.1 基础

左值与右值

当一个对象被用作右值(rvalue)时,用的是对象的值(内容);当一个对象被用作左值(lvalue)时,用的是对象的身份(在内存中的位置)。

  • 赋值需要一个(非常量)左值作为其左边操作数,产生其左操作数,作为左值。
  • 取地址符( & )需要一个左值操作数,返回指向其操作数的指针,作为右值。
  • 内置的解引用运算符( * )、下标运算符( [] )、迭代器解引用运算符( * )、string 和 vector 下标运算符( [] ),都产生左值。
  • 内置和迭代器递增运算符(++)和递减运算符(--)需要左值操作数,其前缀版本也产生左值。

当与 decltype 一起使用时,左值和右值也有所不同。当 decltype 应用于表达式(而不是变量)时,若表达式产生一个左值,则结果为引用类型
例如,假设 p 是 int *。因为解引用运算符会产生一个左值,所以 decltype(*p) 是 int&。另一方面,由于取地址运算符会产生一个右值,因此 decltype(&p) 为 int **,即指向类型为 int 的指针的指针。

4.2 算术运算符

表4.1 算术运算符(左结合律)

运算符+-*/%+-
功能一元正号一元负号乘法除法求余加法减法
使用+ expr- exprexpr * exprexpr / exprexpr % exprexpr + exprexpr - expr

在除法运算中,C++ 语言的早期版本允许对值为负的商进行向上或向下取整;C++11 标准要求商数向 0 取整(即直接截断小数部分)。
取余运算中,如果 m 和 n 是整数且 n 不等于 0,那么 (m/n)*n + m%n = m。如果 m%n 不等于 0,那么它的符号与 m 相同。
除了 -m 溢出的特殊情况,(-m)/nm/(-n) 都等于 -(m/n)m%(-n) 等于 m%n(-m)%n 等于 -(m%n)

4.3 逻辑和关系运算符

表4.2 逻辑和关系运算符

运算符结合律功能使用
!逻辑非!expr
<小于expr < expr
<=小于等于expr <= expr
>大于expr > expr
>=大于等于expr >= expr
==等于expr == expr
!=不等于expr != expr
&&逻辑与expr && expr
||逻辑或expr || expr

4.4 赋值运算符

赋值运算满足右结合律

int ival, jval; 
ival = jval = 0; // ok: each assigned 0

int ival, *pval; // ival is an int; pval is a pointer to int 
ival = pval = 0; // error: cannot assign the value of a pointer to an int 

string s1, s2; 
s1 = s2 = "OK";  // string literal "OK" converted to string

复合赋值运算符

 +=   -=   *=   /=   %=         // arithmetic operators 
<<=  >>=   &=   ^=   |=         // bitwise operators; see § 4.8 (p. 152)

每种复合运算符都等价于 a = a <op> b;
区别:当使用复合赋值运算时,左边操作数只求值一次;如果使用普通的赋值运算,操作数求值两次——一次在右侧的表达式中,令一次是作为左侧的操作数。

4.5 递增和递减运算符

int i = 0, j; 
j = ++i; // j = 1, i = 1: prefix yields the incremented value 
j = i++; // j = 1, i = 2: postfix yields the unincremented value

在一个表达式中结合解引用和递增运算符

auto pbeg = v.begin(); // print elements up to the first negative value 
while (pbeg != v.end() && *beg >= 0)
	cout << *pbeg++ << endl; // print the current value and advance pbeg

*pbeg++ 等价于 *(pbeg++)。最后一条语句等价于

cout << *iter << endl; 
++iter;

C++程序员推荐使用:形如 *pbeg++ 的表达式是一种被广泛使用的、有效的写法。

运算对象可按任意顺序求值

// the behavior of the following loop is undefined! 
while (beg != s.end() && !isspace(*beg))
	*beg = toupper(*beg++);   // error: this assignment is undefined

编译器可能按照下面的其中一种方式求值,或者其他方式:

*beg = toupper(*beg);        // execution if left-hand side is evaluated first 
*(beg + 1) = toupper(*beg);  // execution if right-hand side is evaluated first

4.6 成员访问运算符

string s1 = "a string", *p = &s1; 
auto n = s1.size(); // run the size member of the string s1 
n = (*p).size();    // run size on the object to which p points 
n = p->size();      // equivalent to (*p).size() 

4.7 条件运算符

string finalgrade = (grade < 60) ? "fail" : "pass";

嵌套条件运算符

finalgrade = (grade > 90) ? "high pass"
                          : (grade < 60) ? "fail" : "pass";

条件运算的嵌套不要超过两到三层,否则可读性下降。

条件运算符的优先级非常低

cout << ((grade < 60) ?   "fail" : "pass"); // prints pass or  fail 
cout << (grade < 60) ?   "fail" : "pass";   // prints 1 or 0! 
cout << grade < 60 ?   "fail" : "pass"; //  error: compares cout to 60 

第二条表达式等价于

cout << (grade < 60);    // prints 1 or 0
cout ?  "fail" : "pass"; // test cout and then yield one of the two literals     
						 // depending on whether cout is true or false

第三条表达式等价于

cout << grade;   // less-than has lower precedence than shift, so print grade first 
cout < 60 ? "fail" : "pass"; // then compare cout to 60!

4.8 位运算符

表4.3 位运算符(左结合律)

运算符~<<>>&^|
功能位求反左移右移位与位异或位或
使用~exprexpr << exprexpr >> exprexpr & exprexpr ^ exprexpr | expr

因为如何处理符号位无法确定,所以强烈建议用位运算符处理无符号类型。

unsigned long quiz1 = 0; // we'll use this value as a collection of bits
// "1UL << 27" generate a value with only bit number 27 set
quiz1 |= 1UL << 27; // indicate student number 27 passed
quiz1 &= ~(1UL << 27); // student number 27 failed 
bool status = quiz1 & (1UL << 27); // how did student number 27 do?

移位运算符(又称 IO 运算符)满足左结合律

cout << "hi" << " there" << endl;

上述代码执行过程如下:

( (cout << "hi") << " there" ) << endl;
cout << 42 + 10;   // ok: + has higher 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.9 sizeof 运算符

Sales_data data, *p; 
sizeof(Sales_data); // size required to hold an object of type Sales_data 
sizeof data; // size of data's type, i.e., sizeof(Sales_data) 
sizeof p;    // size of a pointer 
sizeof *p;   // size of the type to which p points, i.e., sizeof(Sales_data) 
sizeof data.revenue; // size of the type of Sales_data's revenue member 
sizeof Sales_data::revenue; // alternative way to get the size of revenue

在 C++11标准下,可以使用作用域运算符来获取类成员的大小。

应用 sizeof 的结果部分取决于所涉及的类型:

  • sizeof char 或 char 类型的表达式,结果确定为 1。
  • sizeof 一个引用类型,返回引用类型的对象的大小。
  • sizeof 一个指针,返回指针所需空间的大小。
  • sizeof 一个解引用的指针,返回该指针指向的类型的对象的大小;指针不必有效。
  • sizeof 一个数组,是整个数组的大小。这等效于 sizeof 元素类型 乘以数组中元素的数量。注意,sizeof 不会将数组转换为指针。
  • sizeof 一个 string 或 vector 对象,仅返回这些类型的固定部分的大小;它不会返回对象元素使用的大小。
vector<int> Profits = { 1,2,3 };
vector<string> Ca = { "/a/b/c","/a/b/ca","/a/b/d","/a/b/c/d" };
vector<int> vec(24);
vector<vector<int>> people;
cout << sizeof(Profits) << endl;
cout << sizeof(Ca) << endl;
cout << sizeof vec << endl;
cout << sizeof people << endl;

在 VS2015 中运行上面的代码时,其结果如下:

16
16
16
16

因为 sizeof 返回整个数组的大小,所以可以通过将数组大小除以元素大小来确定数组中的元素数:

// sizeof(ia)/sizeof(*ia) returns the number of elements in ia 
constexpr size_t sz = sizeof(ia)/sizeof(*ia); 
int arr2[sz];  // ok sizeof returns a constant expression 

4.10 逗号运算符

逗号运算符的结果是右侧表达式的值

cout << (1, 3) << endl; // print 3
vector<int>::size_type cnt = ivec.size(); 
// assign values from size... 1 to the elements in ivec 
for(vector<int>::size_type ix = 0; ix != ivec.size(); ++ix, --cnt)    
	ivec[ix] = cnt;

4.11 类型转换

隐式转换(implicit conversions)出现的情况:

  • 在大多数表达式中,小于 int 的整数类型的值首先被提升为适当的较大整数类型。
  • 在某些情况下,非bool 表达式将转换为 bool。
  • 在初始化中,初始值将转换为变量的类型;在赋值中,右侧的操作数将转换为左侧的类型。
  • 在具有混合类型的操作数的算术和关系表达式中,这些类型将转换为共同类型。
  • 如第 6 章所述,在函数调用期间也会发生转换。
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 + 'a'; //  'a' promoted to int, then that int converted to long double 
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
显式转换

一个命名的强制类型转换(cast)的形式如下:

cast-name<type>(expression);

type 是转换目标类型,expression 是要强制转换的值。如果 type 是引用,那么结果是一个左值。cast-name 指定了执行的转换的类型,是 static_castdynamic_castconst_castreinterpret_cast 其中的一个。

static_cast
可以使用 static_cast 请求,除了包含底层 const 以外的任何定义明确的类型转换。

int i, j; 
// cast used to force floating-point division 
double slope = static_cast<double>(j) / i; 
  • 将较大的算术类型赋值给较小的类型时,可以使用 static_cast。
    通常,将较大的算术类型赋值给较小的类型时,编译器会生成警告。当进行显式强制转换时,警告消息将关闭。

  • 对于编译器不会自动执行的转换,可以使用 static_cast。
    例如,使用 static_cast 取回存储在 void* 指针中的指针值。

void* p = &d;   // ok: address of any nonconst object can be stored in a void* 
// ok: converts void* back to the original pointer type 
double *dp = static_cast<double*>(p);

如果将指针存储在 void* 中,然后使用 static_cast 将指针转换回其原始类型,可以确保保留指针值。即,强制转换的结果等于原始地址值。
但是,必须确保指针转换后的类型是该指针的实际类型。如果类型不匹配,则结果不确定。

const_cast

  • const_cast 仅能更改其运算对象中的底层 const。
const char *pc; 
char *p = const_cast<char*>(pc); // ok: but writing through p is undefined

按照惯例,将 const 对象转换为非const 类型的强制转换行为,称为“去掉 const 性质(cast away the const)”。一旦去掉了对象的 const 性质,编译器将不再阻止对该对象进行写操作。
如果对象最初不是 const,则使用强制转换来获得写访问权限是合法的。但是,为了写入 const 对象,使用 const_cast 是不确定的。

int n = 5;
const int *ip = &n;
int* ip = const_cast<int*>(icp);
cout << *ip << endl;	// prints 5
*ip = 7;
cout << *ip << endl;	// prints 7
  • 只能使用 const_cast 来更改表达式的 const 属性。
  • 不能使用 const_cast 来更改表达式的类型。
const char *cp; 
const char *q = static_cast<char*>(cp); // error: static_cast can't cast away 
static_cast<string>(cp); // ok: converts string literal to string 
const_cast<string>(cp);  // error: const_cast only changes constness 
  • const_cast 常用于重载函数的上下文中。

reinterpret_cast

  • reinterpret_cast 通常对其运算对象的位模式执行底层的重新解释。
int *ip; 
char *pc = reinterpret_cast<char*>(ip);

不要忘记 pc 寻址的实际对象是一个 int,而不是一个字符。任何假设 pc 是普通字符指针的使用,都可能在运行时失败。 例如,下面的代码可能导致异常的运行时行为:

string str(pc);
  • 使用 reinterpret_cast 很危险。问题是类型已更改,但是编译器没有警告或错误。
    当使用 int 的地址初始化 pc 时,编译器不会发出错误或警告,因为明确表示转换是可以的。随后对 pc 的任何使用都假定其值是 char*。编译器无法知道它实际上是一个指向 int 的指针。因此,用pc 初始化 str 是绝对正确的——尽管在这种情况下毫无意义或更糟!
    找出此类问题的原因非常困难,如果将 ip 强制转换为 pc 的语句,与使用 pc 初始化 string 对象的语句,处于不同的文件中,尤其如此。

注意:reinterpret_cast 本质上与机器有关。要想安全地使用 reinterpret_cast 需要完全了解,所涉及的类型以及编译器如何实现强制转换的细节。

旧式强制类型转换
在早期版本的 C++ 语言中,显式进行强制类型转换有两种形式:

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。 例如:

char *pc = (char*) ip; // ip is a pointer to int

上面代码的效果与使用 reinterpret_cast 一样。

建议:避免强制类型转换!

4.12 运算符优先级表

运算符(从上至下,优先级从高到低)
作用域 ::
成员访问 object.member, pointer->member
后置递增递减 lvalue++, lvalue--cast_name<type>expr
前置递增递减 ++lvalue, --lvalue;类型转换 (type)exprsizeof
算术 *, /, %+, -
移位 <<, >>
关系 <, <=, >, >===, !=
&, | , ^
逻辑 &&, ||
条件 ? :
赋值 =
复合赋值 *=, /=, %=+=, -=<<=, >>=&=, |=, ^=
逗号 ,

学习目录:【C++ primer】目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值