首页 > 其他分享 >第四章 表达式

第四章 表达式

时间:2023-02-19 17:35:05浏览次数:60  
标签:int 练习 运算符 ++ 表达式 ival 第四章

第四章 表达式

表达式基础

  • 运算对象转换:小整数类型会被提升为较大的整数类型

  • 重载运算符:当运算符作用在类类型的运算对象时,用户可以自行定义其含义。

  • 左值和右值

    • C中原意:左值可以在表达式左边,右值不能。
    • C++:当一个对象被用作右值的时候,用的是对象的(内容);
    • 被用做左值时,用的是对象的身份(在内存中的位置)。
  • 求值顺序int i = f1() + f2()

    • 先计算f1() + f2(),再计算int i = f1() + f2()。但是f1和f2的计算先后不确定
    • 但是,如果f1、f2都对同一对象进行了修改,因为顺序不确定,所以会编译出错,显示未定义

练习4.1

表达式5 + 10 * 20 / 2的求值结果是多少?

解:

等价于5 + ((10 * 20) / 2) = 105

练习4.2

根据4.12节中的表,在下述表达式的合理位置添加括号,使得添加括号后运算对象的组合顺序与添加括号前一致。
(a) *vec.begin()
(b) *vec.begin() + 1

解:

*(vec.begin())
(*(vec.begin())) + 1

练习4.3

C++语言没有明确规定大多数二元运算符的求值顺序,给编译器优化留下了余地。这种策略实际上是在代码生成效率和程序潜在缺陷之间进行了权衡,你认为这可以接受吗?请说出你的理由。

解:

可以接受。C++的设计思想是尽可能地“相信”程序员,将效率最大化。然而这种思想却有着潜在的危害,就是无法控制程序员自身引发的错误。因此 Java 的诞生也是必然,Java的思想就是尽可能地“不相信”程序员。

算术运算符

  • 溢出:当计算的结果超出该类型所能表示的范围时就会产生溢出。
  • bool类型不应该参与计算
    bool b=true;
    bool b2=-b;   //仍然为true
    //b为true,提升为对应int=1,-b=-1
    //b2=-1≠0,所以b2仍未true
    
  • 取余运算m%n,结果符号与m相同

练习4.4

在下面的表达式中添加括号,说明其求值过程及最终结果。编写程序编译该(不加括号的)表达式并输出结果验证之前的推断。

12 / 3 * 4 + 5 * 15 + 24 % 4 / 2

解:

((12 / 3) * 4) + (5 * 15) + ((24 % 4) / 2) = 16 + 75 + 0 = 91

练习4.5

写出下列表达式的求值结果。

-30 * 3 + 21 / 5  // -90+4 = -86
-30 + 3 * 21 / 5  // -30+63/5 = -30+12 = -18
30 / 3 * 21 % 5   // 10*21%5 = 210%5 = 0
-30 / 3 * 21 % 4  // -10*21%4 = -210%4 = -2

练习4.6

写出一条表达式用于确定一个整数是奇数还是偶数。

解:

if (i % 2 == 0) /* ... */

或者

if (i & 0x1) /* ... */

练习4.7

溢出是何含义?写出三条将导致溢出的表达式。

解:

当计算的结果超出该类型所能表示的范围时就会产生溢出。

short svalue = 32767; ++svalue; // -32768
unsigned uivalue = 0; --uivalue;  // 4294967295
unsigned short usvalue = 65535; ++usvalue;  // 0

逻辑运算符

  • 短路求值:逻辑与运算符和逻辑或运算符都是先求左侧运算对象的值再求右侧运算对象的值,当且仅当左侧运算对象无法确定表达式的结果时才会计算右侧运算对象的值。先左再右
  • 小技巧,声明为引用类型可以避免对元素的拷贝,如下,如string特别大时可以节省大量时间。
vector<string> text;
for(const auto &s: text){
  cout<<s;
}

练习4.8

说明在逻辑与、逻辑或及相等性运算符中运算对象的求值顺序。

解:

  • 逻辑与运算符和逻辑或运算符都是先求左侧运算对象的值再求右侧运算对象的值,当且仅当左侧运算对象无法确定表达式的结果时才会计算右侧运算对象的值。这种策略称为 短路求值
  • 相等性运算符未定义求值顺序。

练习4.9

解释在下面的if语句中条件部分的判断过程。

const char *cp = "Hello World";
if (cp && *cp)

解:

首先判断cpcp 不是一个空指针,因此cp为真。然后判断*cp*cp 的值是字符'H',非0。因此最后的结果为真。

练习4.10

while循环写一个条件,使其从标准输入中读取整数,遇到42时停止。

解:

int i;
while(cin >> i && i != 42)

练习4.11

书写一条表达式用于测试4个值a、b、c、d的关系,确保a大于b、b大于c、c大于d。

解:

a>b && b>c && c>d

练习4.12

假设ijk是三个整数,说明表达式i != j < k的含义。

解:

这个表达式等于i != (j < k)。首先得到j < k的结果为truefalse,转换为整数值是10,然后判断i不等于10 ,最终的结果为bool值。

赋值运算符

  • 赋值运算的返回结果时它的左侧运算对象,且是一个左值。类型也就是左侧对象的类型。
  • 如果赋值运算的左右侧运算对象类型不同,则右侧运算对象将转换成左侧运算对象的类型。
  • 赋值运算符满足右结合律,这点和其他二元运算符不一样。 ival = jval = 0;等价于ival = (jval = 0);
  • 赋值运算优先级比较低,使用其当条件时应该加括号。
  • 复合赋值运算符,复合运算符只求值一次,普通运算符求值两次。(对性能有一点点点点影响)
    任意复合运算符op等价于a = a op b;

练习4.13

在下述语句中,当赋值完成后 i 和 d 的值分别是多少?

int i;   double d;
d = i = 3.5; // i = 3, d = 3.0
i = d = 3.5; // d = 3.5, i = 3

练习4.14

执行下述 if 语句后将发生什么情况?

if (42 = i)   // 编译错误。赋值运算符左侧必须是一个可修改的左值。而字面值是右值。
if (i = 42)   // true.

练习4.15

下面的赋值是非法的,为什么?应该如何修改?

double dval; int ival; int *pi;
dval = ival = pi = 0;

解:
p是指针,不能赋值给int,应该改为:

dval = ival = 0;
pi = 0;

练习4.16

尽管下面的语句合法,但它们实际执行的行为可能和预期并不一样,为什么?应该如何修改?

if (p = getPtr() != 0)
if (i = 1024)

解:

if ((p=getPtr()) != 0)
if (i == 1024)

递增递减运算符

  • 前置版本j = ++i,先加一后赋值
  • 后置版本j = i++,先赋值后加一

优先使用前置版本,后置多一步储存原始值。(除非需要变化前的值)

混用解引用和递增运算符

*iter++等价于*(iter++),递增优先级较高

auto iter = vi.begin();
while (iter!=vi.end()&&*iter>=0)
	cout<<*iter++<<endl;	// 输出当前值,指针向前移1

简介是一种美德,追求简洁能降低程序出错可能性

练习4.17

说明前置递增运算符和后置递增运算符的区别。

解:

前置递增运算符将对象本身作为左值返回,而后置递增运算符将对象原始值的副本作为右值返回。

练习4.18

如果132页那个输出vector对象元素的while循环使用前置递增运算符,将得到什么结果?

解:

将会从第二个元素开始取值,并且最后对v.end()进行取值,结果是未定义的。

练习4.19

假设ptr的类型是指向int的指针、vec的类型是vectorival的类型是int,说明下面的表达式是何含义?如果有表达式不正确,为什么?应该如何修改?

(a) ptr != 0 && *ptr++  
(b) ival++ && ival
(c) vec[ival++] <= vec[ival] 

解:

  • (a) 判断ptr不是一个空指针,并且ptr当前指向的元素的值也为真,然后将ptr指向下一个元素
  • (b) 判断ival的值为真,并且(ival + 1)的值也为真
  • (c) 表达式有误。C++并没有规定<=运算符两边的求值顺序,应该改为vec[ival] <= vec[ival+1]

成员访问运算符

ptr->mem等价于(*ptr).mem

注意.运算符优先级大于*,所以记得加括号

练习4.20

假设iter的类型是vector::iterator, 说明下面的表达式是否合法。如果合法,表达式的含义是什么?如果不合法,错在何处?

(a) *iter++;
(b) (*iter)++;
(c) *iter.empty();
(d) iter->empty();
(e) ++*iter;
(f) iter++->empty();

解:

  • (a)合法。返回迭代器所指向的元素,然后迭代器递增。
  • (b)不合法。因为vector元素类型是string,没有++操作。
  • (c)不合法。这里应该加括号。
  • (d)合法。判断迭代器当前的元素是否为空。
  • (e)不合法。string类型没有++操作。
  • (f)合法。判断迭代器当前元素是否为空,然后迭代器递增。

条件运算符

  • 条件运算符(?:)允许我们把简单的if-else逻辑嵌入到单个表达式中去,按照如下形式:cond? expr1: expr2

  • 可以嵌套使用,右结合律,从右向左顺序组合

    • finalgrade = (grade > 90) ? "high pass"
          : (grade < 60) ? "fail" : "pass";
      //等价于
      finalgrade = (grade > 90) ? "high pass"
          : ((grade < 60) ? "fail" : "pass");
      
  • 输出表达式使用条件运算符记得加括号,条件运算符优先级太低。

练习4.21

编写一段程序,使用条件运算符从vector中找到哪些元素的值是奇数,然后将这些奇数值翻倍。

解:

#include <iostream>
#include <vector>

using std::cout;
using std::endl;
using std::vector;

int main()
{
	vector<int> ivec{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };

	for (auto &i : ivec)
	{
		cout << ((i & 0x1) ? i * 2 : i) << " ";
	}
	cout << endl;

	return 0;
}

练习4.22

本节的示例程序将成绩划分为high passpassfail 三种,扩展该程序使其进一步将 60 分到 75 分之间的成绩设定为low pass。要求程序包含两个版本:一个版本只使用条件运算符;另一个版本使用1个或多个if语句。哪个版本的程序更容易理解呢?为什么?

解:

#include <iostream>
using std::cout; using std::cin; using std::endl;

int main()
{
	for (unsigned g; cin >> g;)
	{
		auto result = g > 90 ? "high pass" : g < 60 ? "fail" : g < 75 ? "low pass" : "pass";
		cout << result << endl;

		// -------------------------
		if (g > 90)         cout << "high pass";
		else if (g < 60)    cout << "fail";
		else if (g < 75)    cout << "low pass";
		else                cout << "pass";
		cout << endl;
	}

	return 0;
}

第二个版本容易理解。当条件运算符嵌套层数变多之后,代码的可读性急剧下降。而if else的逻辑很清晰。

练习4.23

因为运算符的优先级问题,下面这条表达式无法通过编译。根据4.12节中的表指出它的问题在哪里?应该如何修改?

string s = "word";
string pl = s + s[s.size() - 1] == 's' ? "" : "s" ;

解:

加法运算符的优先级高于条件运算符。因此要改为:

string pl = s + (s[s.size() - 1] == 's' ? "" : "s") ;

练习4.24

本节的示例程序将成绩划分为high passpass、和fail三种,它的依据是条件运算符满足右结合律。假如条件运算符满足的是左结合律,求值的过程将是怎样的?

解:

如果条件运算符满足的是左结合律。那么

finalgrade = (grade > 90) ? "high pass" : (grade < 60) ? "fail" : "pass";
等同于
finalgrade = ((grade > 90) ? "high pass" : (grade < 60)) ? "fail" : "pass";
假如此时 grade > 90 ,第一个条件表达式的结果是 "high pass" ,而字符串字面值的类型是 const char *,非空所以为真。因此第二个条件表达式的结果是 "fail"。这样就出现了自相矛盾的逻辑。

位运算符

用于检查和设置二进制位的功能。

  • 位运算符是作用于整数类型的运算对象。
  • 二进制位向左移(<<)或者向右移(>>),移出边界外的位就被舍弃掉了。
  • 位取反(~)(逐位求反)、与(&)、或(|)、异或(^

有符号数负值可能移位后变号,所以强烈建议位运算符仅用于无符号数

应用:

unsigned long quiz1 = 0;    // 每一位代表一个学生是否通过考试
1UL << 12;  // 代表第12个学生通过
quiz1 |= (1UL << 12);   // 将第12个学生置为已通过
quiz1 &= ~(1UL << 12);  // 将第12个学生修改为未通过
bool stu12 = quiz1 & (1UL << 12);   // 判断第12个学生是否通过

位运算符使用较少,但是重载cout、cin大家都用过

位运算符满足左结合律,优先级介于中间,使用时尽量加括号。

练习4.25

如果一台机器上int占32位、char占8位,用的是Latin-1字符集,其中字符'q' 的二进制形式是01110001,那么表达式~'q' << 6的值是什么?

解:

首先将char类型提升为int类型,即00000000 00000000 00000000 01110001,然后取反,再左移6位,结果是-7296。

练习4.26

在本节关于测验成绩的例子中,如果使用unsigned int 作为quiz1 的类型会发生什么情况?

解:

在有的机器上,unsigned int 类型可能只有 16 位,因此结果是未定义的。

练习4.27

下列表达式的结果是什么?

unsigned long ul1 = 3, ul2 = 7;
(a) ul1 & ul2 
(b) ul1 | ul2 
(c) ul1 && ul2
(d) ul1 || ul2 

解:

  • (a) 3
  • (b) 7
  • (c) true
  • (d) ture

sizeof运算符

  • 返回一条表达式或一个类型名字所占的字节数
  • 返回的类型是 size_t的常量表达式。
  • sizeof并不实际计算其运算对象的值。
  • 两种形式:
    1. sizeof (type),给出类型名
    2. sizeof expr,给出表达式
  • 可用sizeof返回数组的大小
int ia[10];
// sizeof(ia)返回整个数组所占空间的大小
// sizeof(ia)/sizeof(*ia)返回数组的大小
constexpr size_t sz = sizeof(ia)/sizeof(*ia);
int arr[sz];

练习4.28

编写一段程序,输出每一种内置类型所占空间的大小。

解:

#include <iostream> 

using namespace std;

int main()
{
	cout << "bool:\t\t" << sizeof(bool) << " bytes" << endl << endl;

	cout << "char:\t\t" << sizeof(char) << " bytes" << endl;
	cout << "wchar_t:\t" << sizeof(wchar_t) << " bytes" << endl;
	cout << "char16_t:\t" << sizeof(char16_t) << " bytes" << endl;
	cout << "char32_t:\t" << sizeof(char32_t) << " bytes" << endl << endl;

	cout << "short:\t\t" << sizeof(short) << " bytes" << endl;
	cout << "int:\t\t" << sizeof(int) << " bytes" << endl;
	cout << "long:\t\t" << sizeof(long) << " bytes" << endl;
	cout << "long long:\t" << sizeof(long long) << " bytes" << endl << endl;

	cout << "float:\t\t" << sizeof(float) << " bytes" << endl;
	cout << "double:\t\t" << sizeof(double) << " bytes" << endl;
	cout << "long double:\t" << sizeof(long double) << " bytes" << endl << endl;

	return 0;
}

输出:

bool:           1 bytes

char:           1 bytes
wchar_t:        4 bytes
char16_t:       2 bytes
char32_t:       4 bytes

short:          2 bytes
int:            4 bytes
long:           8 bytes
long long:      8 bytes

float:          4 bytes
double:         8 bytes
long double:    16 bytes

练习4.29

推断下面代码的输出结果并说明理由。实际运行这段程序,结果和你想象的一样吗?如不一样,为什么?

int x[10];   int *p = x;
cout << sizeof(x)/sizeof(*x) << endl;
cout << sizeof(p)/sizeof(*p) << endl;

解:

第一个输出结果是 10。第二个结果1,此处用法不合理不是未定义,参考https://www.geeksforgeeks.org/using-sizof-operator-with-array-paratmeters/。

练习4.30

根据4.12节中的表,在下述表达式的适当位置加上括号,使得加上括号之后的表达式的含义与原来的含义相同。

(a) sizeof x + y      
(b) sizeof p->mem[i]  
(c) sizeof a < b     
(d) sizeof f() 

解:

(a) (sizeof x) + y
(b) sizeof(p->mem[i])
(c) sizeof(a) < b
(d) sizeof(f())

逗号运算符

从左向右依次求值。

左侧求值结果丢弃,逗号运算符结果是右侧表达式的值。

练习4.31

本节的程序使用了前置版本的递增运算符和递减运算符,解释为什么要用前置版本而不用后置版本。要想使用后置版本的递增递减运算符需要做哪些改动?使用后置版本重写本节的程序。

解:

在4.5节(132页)已经说过了,除非必须,否则不用递增递减运算符的后置版本。在这里要使用后者版本的递增递减运算符不需要任何改动。

练习4.32

解释下面这个循环的含义。

constexpr int size = 5;
int ia[size] = { 1, 2, 3, 4, 5 };
for (int *ptr = ia, ix = 0;
    ix != size && ptr != ia+size;
    ++ix, ++ptr) { /* ... */ }

解:

这个循环在遍历数组ia,指针ptr和整型ix都是起到一个循环计数的功能。

练习4.33

根据4.12节中的表说明下面这条表达式的含义。

someValue ? ++x, ++y : --x, --y

解:

逗号表达式的优先级是最低的。因此这条表达式也等于:

(someValue ? ++x, ++y : --x), --y

如果someValue的值为真,xy 的值都自增并返回 y 值,然后丢弃y值,y递减并返回y值。如果someValue的值为假,x 递减并返回x 值,然后丢弃x值,y递减并返回y值。

类型转换

隐式类型转换

设计为尽可能避免损失精度,即转换为更精细类型。

  • int类型小的整数值先提升为较大的整数类型。
  • 条件中,非布尔转换成布尔。
  • 初始化中,初始值转换成变量的类型。
  • 算术运算或者关系运算的运算对象有多种类型,要转换成同一种类型。
  • 函数调用时也会有转换。

算术转换

整型提升
  • 常见的char、bool、short能存在int就会转换成int,否则提升为unsigned int
  • wchar_t,char16_t,char32_t提升为整型中int,long,long long ……最小的,且能容纳原类型所有可能值的类型。

练习4.34

根据本节给出的变量定义,说明在下面的表达式中将发生什么样的类型转换:

(a) if (fval)
(b) dval = fval + ival;
(c) dval + ival * cval;

需要注意每种运算符遵循的是左结合律还是右结合律。

解:

(a) fval 转换为 bool 类型
(b) ival 转换为 float ,相加的结果转换为 double
(c) cval 转换为 int,然后相乘的结果转换为 double

练习4.35

假设有如下的定义:

char cval;
int ival;
unsigned int ui;
float fval;
double dval;

请回答在下面的表达式中发生了隐式类型转换吗?如果有,指出来。

(a) cval = 'a' + 3;
(b) fval = ui - ival * 1.0;
(c) dval = ui * fval;
(d) cval = ival + fval + dval;

解:

  • (a) 'a' 转换为 int ,然后与 3 相加的结果转换为 char
  • (b) ival 转换为 doubleui 转换为 double,结果转换为 float
  • (c) ui 转换为 float,结果转换为 double
  • (d) ival 转换为 float,与fval相加后的结果转换为 double,最后的结果转换为char

其他转换

p143

显式类型转换(尽量避免)

  • static_cast:任何明确定义的类型转换,只要不包含底层const,都可以使用。 double slope = static_cast<double>(j);

  • dynamic_cast:支持运行时类型识别。

  • const_cast:只能改变运算对象的底层const,一般可用于去除const性质。 const char *pc; char *p = const_cast<char*>(pc)

    只有其可以改变常量属性

  • reinterpret_cast:通常为运算对象的位模式提供低层次上的重新解释。

旧式强制类型转换

type expr

练习4.36

假设 iint类型,ddouble类型,书写表达式 i*=d 使其执行整数类型的乘法而非浮点类型的乘法。

解:

i *= static_cast<int>(d);

练习4.37

练习4.37
用命名的强制类型转换改写下列旧式的转换语句。

int i; double d; const string *ps; char *pc; void *pv;
(a) pv = (void*)ps;
(b) i = int(*pc);
(c) pv = &d;
(d) pc = (char*)pv;

解:

(a) pv = static_cast<void*>(const_cast<string*>(ps));
(b) i = static_cast<int>(*pc);
(c) pv = static_cast<void*>(&d);
(d) pc = static_cast<char*>(pv);

练习4.38

说明下面这条表达式的含义。

double slope = static_cast<double>(j/i);

解:

j/i的结果值转换为double,然后赋值给slope

运算符优先级表

p147

标签:int,练习,运算符,++,表达式,ival,第四章
From: https://www.cnblogs.com/Epiephany/p/17135131.html

相关文章

  • 正则表达式
    \将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。例如,n匹配字符n。\n匹配一个换行符。序列\\匹配\而\(则匹配(。^匹配输入字......
  • XXL-JOB 分布式任务调度框架(Cron表达式、环境搭建、整合SpringBoot、广播任务与动态分
    (目录)xxl-Job分布式任务调度1.概述1.1什么是任务调度我们可以先思考一下业务场景的解决方案:某电商系统需要在每天上午10点,下午3点,晚上8点发放一批优惠券。某银行......
  • python正则表达式
    正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配。python中提供了re模块用于正则表达式的匹配1、re.findall:在字符串中找到正则表达式所......
  • shell正则表达式和awk
    一、正则表达式注意事项:使用正则表达式必须加引号。正则表达式主要用来匹配字符串(命令结果,文本内容) 通配符匹配文件(而且是已存在的文件)基本正则表达式扩展正则......
  • quartz Cron 表达式包括以
    转的: Cron表达式包括以下7个字段:秒分小时月内日期月周内日期年(可选字段)特殊字符Cron触发器利用一系列特殊字符,如下所示:反斜线(/)字符表示增量值。例如,在秒字段中......
  • 正则表达式
    正则表达式:匹配字符串能做什么:1.检测输入的字符串是否合法用户输入一个内容时,我们要提前做检测能够提高程序的效率且减轻服务器压力2.从一个大文......
  • 第四章:欢声存留,笑语不散
    阡陌交错、错综复杂;岛岛相连,路路相通。下方是深不见底的探渊,渊上浮着一层浓厚的迷雾。岳阳随着仙灵少女,通过一道道仙家机关,在这偌大的洞府中穿梭。赶路是无聊且疲劳的。途......
  • (数据库系统概论|王珊)第四章数据库安全性:习题
    ​​pdf下载:密码7281​​​​•专栏目录首页:【专栏必读】(考研复试)数据库系统概论第五版(王珊)专栏学习笔记目录导航及课后习题答案详解​​名词解释数据库安全性:保护数据库以......
  • (数据库系统概论|王珊)第四章数据库安全性:习题
    pdf获取:密码7281专栏目录首页:【专栏必读】(考研复试)数据库系统概论第五版(王珊)专栏学习笔记目录导航及课后习题答案详解名词解释数据库安全性:保护数据库以防止不合法......
  • (数据库系统概论|王珊)第四章数据库安全性-第二、三、四、五、六节:数据库安全性控制
    ​​pdf下载:密码7281​​​​专栏目录首页:【专栏必读】(考研复试)数据库系统概论第五版(王珊)专栏学习笔记目录导航及课后习题答案详解​​数据库相关的安全性措施主要有用户身......