C++ primer第四章—表达式

4.1 基础

4.1.1 基本概念

左值和右值

  1. 当一个对象被用作右值的时候,用的是对象的值(内容)
  2. 当一个对象被用作左值的时候,用的是对象的身份(在内存中的位置)。
  3. 其中,需要右值的地方可以用左值来代替,但是不能把右值当成左值(也就是位置)使用。
  • 赋值运算符需要一个非常量左值作为其左侧运算对象,得到的结果也仍然是一个左值。
  • 取地址符作用于一个左值运算对象,返回指向该运算对象的指针,该指针是一个右值。
  • 内置解引用运算符、下标运算符、迭代器解引用运算符、string 和 vector 的下标运算符都返回左值。
  • 内置类型和迭代器的递增递减运算符作用于左值运算对象,其前置版本返回左值,后置版本返回右值。
  • 如果 decltype 作用于一个求值结果是左值的表达式,会得到一个引用类型。

优先级和结合律

  1. 对于那些没有指定执行顺序的运算符来说,如果表达式指向并修改了同一个对象,将会引发错误并产生未定义的行为。
int i = 0;
cout << i << " " << ++i << endl;    // undefined
  1. 有4 种运算符明确规定了运算对象的求值顺序:
  • 逻辑与 ( && ) 运算符
  • 逻辑或 ( || ) 运算符
  • 条件 ( ? : ) 运算符
  • 逗号 ( , ) 运算符
  1. 函数调用顺序没有明确的规定,运算对象的求值顺序与优先级和结合律无关
  2. 处理复合表达式时建议遵循以下两点:
  • 不确定求值顺序时最好使用括号来强制让表达式的组合关系符合程序逻辑的要求
  • 如果表达式改变了某个运算对象的值,则在表达式的其他位置不要再使用这个运算对象

4.2 算术运算符

  1. 在表达式求值之前,小整数类型的运算对象被提升成较大整数类型,所有运算对象最终会转换成同一类型

4.3 逻辑和关系运算符

  1. 将s声明为常量引用是因为text的元素是string对象,可能非常大,将s声明成引用类型可以避免对元素的拷贝;又因为不需要对string对象做写操作,所以将s声明成常量的引用
// s 是对常量的引用;元素既没有被拷贝也不会被改变
for (const auto &s : text){	// 对于text 的每个元素
	cout << s; 				// 输出当前元素
	// 遇到空字符亭或者以句号结束的字符串进行换行
	if (s.empty() || s[s.size() - 1] == '.')
		cout << endl;
	else
		cout << " "; 		// 否则用空格隔开
}
  1. 进行比较运算时,除非比较的对象是布尔类型,否则不要使用布尔字面值 true 和 false 作为运算对象。

4.4 赋值运算符

  1. 注意初始化和赋值的区别
int i = 0, j = 0, k = 0;	// 初始化而非赋值
const int ci = i;			// 初始化而非赋值
  1. 因为赋值运算符的优先级低于关系运算符的优先级,所以在条件语句中,赋值部分通常应该加上括号。
// 这是一种形式烦琐、容易出错的写法
int i = get_value(); // 得到第一个值
while (i != 42)
{
	// 其他处理......
	i = get_value(); // 得到剩下的值
}

// 更好的写法:条件部分表达得更加清晰
int i;
while ((i = get_value()) != 42)   //注意这种写法
{
	// 其他处理......
}

4.5 递增和递减运算符

  1. 除非必须,否则不应该使用递增或递减运算符的后置版本。因为后置版本需要将原始值存储下来以便于返回修改前的内容,如果我们不需要这个值,那么后置版本的操作就是一种浪费。
  2. 后置递增运算符的优先级高于解引用运算符,因此 *pbeg++ 等价于 *(pbeg++)。pbeg++ 把 pbeg 的值加1, 然后返回 pbeg 的初始值的副本作为其求值结果,此时解引用运算符的运算对象是 pbeg 未增加之前的值。最终,这条语句输出 pbeg 开始时指向的那个元素,并将指针向前移动一个位置。
auto pbeg = v.begin();
// 输出元素直至遇到第一个负值为止
while (pbeg != v.end() && *beg >= 0)
	// 输出当前值并将pbeg向前移动一个元素
	cout << *pbeg++ << endl;

cout << *iter++ << endl;  //这种写法更加简洁明了
// cout << *iter << endl;
// ++iter;

4.6 成员访问运算符

  1. 点运算符 . 和箭头运算符 -> 都可以用来访问成员,其中,点运算符获取类对象的一个成员;箭头运算符与点运算符有关,表达式 ptr->mem 等价于 (*ptr).mem。
string s1 = "a string", *p = &s1;   //注意这里要取地址
auto n = s1.size(); // 运行string对象s1的size成员
n = (*p).size();    // 运行p所指对象的size成员
n = p->size();      // 等价于(*p).size()

4.7 条件运算符

  1. 条件运算符的优先级非常低,因此当一个长表达式中嵌套了条件运算子表达式时,通常需要在它两端加上括号。
cout << ((grade < 60) ? "fail" : "pass"); 	// 输出pass或者fail

cout << (grade < 60) ? "fail" : "pass"; 	// 输出1或者0
// 等价于<=>
// cout << (grade < 60); 					// 输出1或者0
// cout ? "fail" : "pass"; 					// 根据cout的值是true还是false产生对应的字面值

cout << grade < 60 ? "fail" : "pass"; 		// 错误:试图比较cout和60
// 等价于<=>
// cout << grade; 							// 小于运算符的优先级低于移位运算符,所以先输出grade
// cout < 60 ? "fail" : "pass"; 			// 然后比较cout和60

4.8 位运算符

位运算符采用左结合律
在这里插入图片描述

  1. 在位运算中符号位如何处理并没有明确的规定,所以强烈建议仅将位运算符用于无符号类型的处理。
  2. 左移运算符 << 在运算对象右侧插入值为0的二进制位,右移运算符 >> 的行为依赖于其左侧运算对象的类型:如果该运算对象是无符号类型,在其左侧插入值为0的二进制位;如果是带符号类型,在其左侧插入符号位的副本或者值为0的二进制位,如何选择视具体环境而定。
  3. char 类型的运算对象首先提升成 int 类型,提升时运算对象原来的位保持不变, 往 高位(high order position) 添0即可。
  4. 移位运算符的优先级不高不低,介于中间:比算术运算符的优先级低,但比关系运算符、赋值运算符和条件运算符的优先级高。
cout << 42 + 10; 	// 正确:+的优先级更高,因此输出求和结果
cout << (10 < 42); 	// 正确:括号使运算对象按照我们的期望组合在一起,输出1
cout << 10 < 42; 	// 错误:试图比较cout和42!
// 等价于<=>
// (cout << 10) < 42;

4.9 sizeof运算符

  1. sizeof 运算符返回一个表达式或一个类型名字所占的字节数,返回值是 size_t 类型。
Sales_data data, *p;
sizeof(Sales_data); 			// 存储Sales_data类型的对象所占的空间大小
sizeof data;					// data的类型的大小,即sizeof(Sales_data)
sizeof p;						// 指针所占的空间大小
sizeof *p;						// p所指类型的空间大小,即sizeof(Sales_data)
sizeof data.revenue; 			// Sales_data的revenue成员对应类型的大小
sizeof Sales_data::revenue; 	// 另一种获取revenue大小的方式
  1. 在 sizeof 的运算对象中解引用一个无效指针仍然是一种安全的行为,因为指针实际上并没有被真正使用。
  2. sizeof 运算符的结果部分依赖于其作用的类型:
  • 对 char 或者类型为 char 的表达式执行 sizeof 运算,返回值为1。
  • 对引用类型执行 sizeof 运算得到被引用对象所占空间的大小。
  • 对指针执行 sizeof 运算得到指针本身所占空间的大小。
  • 对解引用指针执行 sizeof 运算得到指针指向的对象所占空间的大小,指针不需要有效。
  • 对数组执行 sizeof 运算得到整个数组所占空间的大小。
  • 对 string 或 vector 对象执行 sizeof 运算只返回该类型固定部分的大小,不会计算对象中元素所占空间的大小。

4.10 逗号运算符

  1. 逗号运算符 , 含有两个运算对象,按照从左向右的顺序依次求值,最后返回右侧表达式的值

4.11 类型转换

  1. 在C++语言中,某些类型之间有关联。如果两种类型有关联, 那么当程序需要其中一种类型的运算对象时,可以用另一种关联类型的对象或值来替代。换句话说,如果两种类型可以 相互转换(conversion),那么它们就是关联的。
  2. 编译器会自动地转换运算对象的类型:
  • 在大多数表达式中,比 int 类型小的整型值首先提升为较大的整数类型。
  • 在条件中,非布尔值转换成布尔类型。
  • 在初始化过程中,初始值转换成变量的类型;在赋值语句中,右侧运算对象转换成左侧运算对象的类型。
  • 如果算术运算或关系运算的运算对象有多种类型,需要转换成同一种类型。
  • 函数调用时也会发生类型转换。

4.11.1 算术转换

  1. 整型提升(integral promotions) 负责把小整数类型转换成较大的整数类型。如果某个运算符的运算对象类型不一致, 这些运算对象将转换成同一种类型。但是如果某个运算对象的类型是无符号类型,那么转换的结果就要依赖于机器中各个整数类型的相对大小了。

4.11.2 其他隐式转换

  1. 数组转换成指针:在大多数表达式中,数组名字自动转换成指向数组首元素的指针。(注意数组名可以转换成首元素指针,但字符串名不能)
int ia[10]; 	// 含有10个整数的数组
int* ip = ia;	// ia转换成指向放组首元素的指针
  1. 指针的转换:常量整数值0或字面值 nullptr 能转换成任意指针类型;指向任意非常量的指针能转换成 void*;指向任意对象的指针能转换成 const void*。
  2. 转换成布尔类型:任意一种算术类型或指针类型都能转换成布尔类型。如果指针或算术类型的值为0,转换结果是 false,否则是 true。
  3. 转换成常量:允许将指向非常量类型的指针转换成指向相应的常量类型的指针,对于引用也是这样。(但反之不允许)
int i;
const int &j = i;	// 非常量转换成const int的引用
const int *p = &i;	// 非常量的地址转换成const的地址
int &r = j, *q = p;	// 错误:不允许const转换成非常量
  1. 类类型定义的转换:类类型能定义由编译器自动执行的转换,不过编译器每次只能执行一种类类型的转换。
string s, t = "a value";	// 字符串字而位转换成string类型
while (cin >> s)			// while的条件部分把cin转换成布尔值

4.11.3 显示转换

  1. 虽然有时不得不使用强制类型转换,但这种方法本质上是非常危险的。建议尽量避免强制类型转换。
  2. 转换类型:
  • static_cast:任何具有明确定义的类型转换,只要不包含底层 const,都能使用 static_cast。当需要把一个较大的算术类型赋值给较小的类型时,static cast 非常有用。static cast 对于编译器无法自动执行的类型转换也非常有用。
void* p = &d;   //任何非常量对象的地址都能存入void*
double *dp = static_cast<double*>(p);    //void*转换为初始指针类型
  1. const_cast:只能改变运算对象的底层 const,同时也只有 const_cast 能改变表达式的常量属性。const_cast 常常用于函数重载。
  2. reinterpret_cast:通常为运算对象的位模式提供底层上的重新解释。reinterpret_cast 本质上依赖于机器。要想安全地使用reinterpret_cast 必须对涉及的类型和编译器实现转换的过程都非常了解。
  3. dynamic_cast:支持运行时类型识别。

4.12 运算符优先级表

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值