2.1 节练习
练习 2.1
在 C++语言中,int、long、long long 和 short 都属于整型,区别是 C++标准规定的尺寸的最小值(即该类型在内存中所占的比特数)不同。其中,short是短整型,占 16 位;int 是整型,占 16 位;long 和 long long 均为长整型,分别占 32 位和 64 位。C++标准允许不同的编译器赋予这些类型更大的尺寸。某一类型占的比特数不同,它所能表示的数据范围也不一样。
大多数整型都可以划分为无符号类型和带符号类型,在无符号类型中所有比特
都用来存储数值,但是仅能表示大于等于 0 的值;带符号类型则可以表示正数、负数或 0。
float 和 double 分别是单精度浮点数和双精度浮点数,区别主要是在内存中所占的比特数不同,以及默认规定的有效位数不同。
练习 2.2
在实际应用中,利率、本金和付款既有可能是整数,也有可能是普通的实数。
因此应该选择一种浮点类型来表示。在三种可供选择的浮点类型 float、double和 long double 中,double 和 float 的计算代价比较接近且表示范围更广,long double 的计算代价则相对较大,一般情况下没有选择的必要。综合以上分析,选择double 是比较恰当的。
练习 2.3
u 和 u2 都是无符号整数,因此 u2-u 得到了正确的结果(42−10=32);
u-u2 也能正确计算,但是因为直接计算的结果是-32,所以在表示为无符号整数时自动加上了模,在此编译环境中 int 占 32 位,因此加模的结果是 4294967264。
i 和 i2 都是带符号整数,因此中间两个式子的结果比较直观,42-10=32,
10-42=−32。
在最后两个式子中,u 和 i 分别是无符号整数和带符号整数,计算时编译器先把带符号数转换为无符号数,幸运的是,i 本身是一个正数,因此转换后不会出现异常情况,两个式子的计算结果都是 0。
不过需要注意的是,一般情况下请不要在同一个表达式中混合使用无符号类型和带符号类型。因为计算前带符号类型会自动转换成无符号类型,当带符
号类型取值为负时就会出现异常结果。
练习 2.4
见练习 2.3。
练习 2.5
(a) 'a'表示字符 a,L'a'表示宽字符型字面值 a 且类型是 wchar_t,"a"表示字符串 a,L"a"表示宽字符型字符串 a。
(b) 10 是一个普通的整数类型字面值,10u 表示一个无符号数,10L 表示一个长整型数,10uL 表示一个无符号长整型数,012 是一个八进制数(对应的十进制数是 10),0xC 是一个十六进制数(对应的十进制数是 12)。
(c) 3.14 是一个普通的浮点类型字面值,3.14f 表示一个 float 类型的单精度浮点数,3.14L 表示一个 long double 类型的扩展精度浮点数。
(d) 10 是一个整数,10u 是一个无符号整数,10.是一个浮点数,10e-2 是一个科学计数法表示的浮点数,大小为 10*10^-2=0.1。
附上书上的两张图
练习 2.6
第一组定义是正确的,定义了两个十进制数 9 和 7。
第二组定义是错误的,编译时将报错。因为以 0 开头的数是八进制数,而数字 9显然超出了八进制数能表示的范围,所以第二组定义无法被编译通过。
练习 2.7
(a)是一个字符串,包含两个转义字符,其中\145 表示字符 e,\012 表示一个换行符,因此该字符串的输出结果是 Who goes with Fergus?
结合书上的原话去理解会更好:
-
我们也可以使用泛化的转义序列,其形式是\x后紧跟1个或多个十六进制数字,或者\后紧跟1个、2个或3个八进制数字,其中数字部分表示的是字符对应的数值。
-
注意,如果反斜线\后面跟着的八进制数字超过3个,只有前3个数字与\构成转义序列。例如,"\1234"表示2个字符,即八进制数123对应的字符以及字符4。相反,\x要用到后面跟着的所有数字,例如,"\x1234"表示一个16位的字符,该字符由这4个十六进制数所对应的比特唯一确定。因为大多数机器的char型数据占8位,所以上面这个例子可能会报错。一般来说,超过8位的十六进制字符都是与表22中某个前缀作为开头的扩展字符集一起使用的。
(b)是一个科学计数法表示的扩展精度浮点数,大小为 3.14*10L=31.4。
(c)试图表示一个单精度浮点数,但是该形式在某些编译器中将报错,因为后缀 f 直接跟在了整数 1024 后面;改写成 1024.f 就可以了。
(d)是一个扩展精度浮点数,类型是 long double,大小为 3.14。
练习 2.8
其中,字符串"2\x4d\012"先输出字符 2,紧接着利用转义字符\x4d 输出字
符 M,最后利用转义字符\012 转到新一行。
字符串"2\tM\n"先输出字符 2,然后利用转义字符\t 输出一个制表符,接着输出字符 M(大写字母A的ASCII码是65,小写字母的a的ASCII码是97),最后利用转义字符\n 转到新一行。
可以发现,输出同一个字符有多种方式可供选择。例如,可以直接输出字
符 M,也可以通过转义字符\x4d 输出字符 M;可以用转义字符\012 换行,也可以用转义字符\n 换行。
2.2 节练习
练习 2.9
(a)是错误的,输入运算符的右侧需要一个明确的变量名称,而非定义变量的语句,
改正后的结果是:
int input_value;
std::cin >> input_value;
(b)是错误的,该语句定义了一个整型变量 i,但是试图通过列表初始化的方式把浮点数3.14赋值给i,这样做将造成小数部分丢失,是一种不被建议的窄化操作。
附上书上的原话:
(c)是错误的,该语句试图将 9999.99 分别赋值给 salary 和 wage,但是在声明语句中声明多个变量时需要用逗号将变量名隔开,而不能直接用赋值运算符连接,
改正后的结果是:
double salary, wage;
salary = wage = 9999.99;
(d)引发警告,该语句定义了一个整型变量 i,但是试图把浮点数 3.14 赋值给i,这样做将造成小数部分丢失,与(b)一样是不被建议的窄化操作。
确实引发了一个警告
练习 2.10
对于 string 类型的变量来说,因为 string 类型本身接受无参数的初始化方式,所以不论变量定义在函数内还是函数外都被默认初始化为空串。
对于内置类型 int 来说,变量 global_int 定义在所有函数体之外,根据 C++的规定,global_int 默认初始化为 0;而变量 local_int 定义在 main 函数的内部,将不被初始化,如果程序试图拷贝或输出未初始化的变量,将遇到一个未定义的奇异值。
练习 2.11
声明与定义的关系是:声明使得名字为程序所知,而定义负责创建与名字关联
的实体。(a)定义了变量 ix,(b)声明并定义了变量 iy,(c)声明了变量iz。
书上原话:
练习 2.12
(a)是非法的,因为 double 是 C++关键字,代表一种数据类型,不能作为变量的名字。
(c)是非法的,在标识符中只能出现字母、数字和下画线,不能出现符号-,如果改成“int catch_22;”就是合法的了。
(d)是非法的,因为标识符必须以字母或下画线开头,不能以数字开头。
(b)和(e)是合法的命名。
练习 2.13
j 的值是 100。C++允许内层作用域重新定义外层作用域中已有的名字,在本题中,int i=42;位于外层作用域,但是变量 i 在内层作用域被重新定义了,因此真正赋予 j 的值是定义在内层作用域中的 i 的值,即 100。
练习 2.14
该程序是合法的,输出结果是 100 45。
该程序存在嵌套的作用域,其中 for 循环之外是外层作用域,for 循环内部是内层作用域。首先在外层作用域中定义了 i 和 sum,但是在 for 循环内部 i 被重新定义了,因此 for 循环实际上是从 i=0 循环到了 i=9,内层作用域中没有重新定义sum,因此 sum 的初始值是 0 并在此基础上依次累加。最后一句输出语句位于外层作用域中,此时在 for 循环内部重新定义的 i 已经失效,因此实际输出的仍然是外层作用域的 i,值为 100;而 sum 经由循环累加,值变为了 45。
2.3 节练习
练习 2.15
(b)是非法的,引用必须指向一个实际存在的对象而非字面值常量。
(d)是非法的,因为我们无法令引用重新绑定到另外一个对象,所以引用必须初始化。
(a)和(c)是合法的。
练习 2.16
(a)是合法的,为引用赋值实际上是把值赋给了与引用绑定的对象,在这里是把3.14159 赋给了变量 d。
(b)是合法的,以引用作为初始值实际上是以引用绑定的对象作为初始值,在这里是把 i 的值赋给了变量 d。
(c)是合法的,把 d 的值赋给了变量 i,因为 d 是双精度浮点数而 i 是整数,所以该语句实际上执行了窄化操作。
(d)是合法的,把 d 的值赋给了变量 i,与上一条语句一样执行了窄化操作。
练习 2.17
程序的输出结果是 10 10。
引用不是对象,它只是为已经存在的对象起了另外一个名字,因此 ri 实际上是i 的别名。在上述程序中,首先将 i 赋值为 5,然后把这个值更新为 10。因为 ri是 i 的引用,所以它们的输出结果是一样的。
练习 2.18
在上述示例中,首先定义了两个整型变量 i 和 j 以及一个整型指针 p,初始情况下令指针 p 指向变量 i,此时分别输出 p 的值(即 p 所指对象的内存地址)以及 p 所指对象的值,得到 00E8FCEC 和 5。
随后依次更改指针的值以及指针所指对象的值。p=&j;更改了指针的值,令指
针p指向另外一个整数对象j。*p=20;和j=30是两种更改指针所指对象值的方式,前者显式地更改指针 p 所指的内容,后者则通过更改变量 j 的值实现同样的目的。
练习 2.19
指针“指向”内存中的某个对象,而引用“绑定到”内存中的某个对象,它们
都实现了对其他对象的间接访问,二者的区别主要有两方面:
第一,指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周
期内它可以指向几个不同的对象;引用不是一个对象,无法令引用重新绑定到另外一个对象。
第二,指针无须在定义时赋初值,和其他内置类型一样,在块作用域内定义的
指针如果没有被初始化,也将拥有一个不确定的值;引用则必须在定义时赋初值。
补充:
如果指针被定义为全局变量,那么它的初始值为 0
练习 2.20
这段代码首先定义了一个整型变量 i 并设其初值为 42;接着定义了一个整型指针 pl,令其指向变量 i;最后取出 pl 所指的当前值,计算平方后重新赋给 pl 所指的变量 i。
第二行的表示声明一个指针,第三行的表示解引用运算,即取出指针 pl 所指对象的值。
练习 2.21
(a)是非法的,dp 是一个 double 指针,而 i 是一个 int 变量,类型不匹配。
(b)是非法的,不能直接把 int 变量赋给 int 指针,正确的做法是通过取地址运算&i 得到变量 i 在内存中的地址,然后再将该地址赋给指针。
(c)是合法的。
练习 2.22
指针 p 作为 if 语句的条件时,实际检验的是指针本身的值,即指针所指的地址值。如果指针指向一个真实存在的变量,则其值必不为 0,此时条件为真;如果指针没有指向任何对象或者是无效指针,则对 p 的使用将引发不可预计的结果。
解引用运算符*p 作为 if 语句的条件时,实际检验的是指针所指的对象内容,在上面的示例中是指针 p 所指的 int 值。如果该 int 值为 0,则条件为假;否则,如果该 int 值不为 0,对应条件为真。
练习 2.23
在 C++程序中,应该尽量初始化所有指针,并且尽可能等定义了对象之后再定义指向它的指针。如果实在不清楚指针应该指向何处,就把它初始化为 nullptr 或者 0,这样程序就能检测并知道它有没有指向一个具体的对象了。
其中,nullptr是 C++11 新标准刚刚引入的一个特殊字面值,它可以转换成任意其他的指针类型。在此前提下,判断 p 是否指向合法的对象,只需把 p 作为 if 语句的条件即可,如果p 的值是 nullptr,则条件为假;反之,条件为真。
如果不注意初始化所有指针而贸然判断指针的值,则有可能引发不可预知的结
果。一种处理的办法是把 if(p)置于 try 结构中,当程序块顺利执行时,表示 p 指向了合法的对象;当程序块出错跳转到 catch 语句时,表示 p 没有指向合法的对象。
练习 2.24
p 是合法的,因为 void*是一种特殊的指针类型,可用于存放任意对象的地址。
lp 是非法的,因为 lp 是一个长整型指针,而 i 只是一个普通整型数,二者的类型不匹配。
练习 2.25
(a)ip 是一个整型指针,指向一个整型数,它的值是所指整型数在内存中的地址;i是一个整型数;r 是一个引用,它绑定了 i,可以看作是 i 的别名,r 的值就是 i 的值。
(b)i 是一个整型数;ip 是一个整型指针,但是它不指向任何具体的对象,它的值被初始化为 0。
(c)ip是一个整型指针,指向一个整型数,它的值是所指整型数在内存中的地址;ip2 是一个整型数。
2.4 节练习
练习 2.26
本题的所有语句应该被看作是顺序执行的,即形如:
const int buf;
int cnt = 0;
const int sz = cnt;
++cnt;
++sz;
(a)是非法的,const 对象一旦创建后其值就不能改变,所以 const 对象必须初始化。该句应修改为 const int buf = 10。
(b)和(c)是合法的。
(d)是非法的,sz 是一个 const 对象,其值不能被改变,当然不能执行自增操作。
练习 2.27
(a)是非法的,非常量引用 r 不能引用字面值常量 0。
(b)是合法的,p2 是一个常量指针,p2 的值永不改变,即 p2 永远指向变量 i2。
(c)是合法的,i 是一个常量,r 是一个常量引用,此时 r 可以绑定到字面值常量 0。
(d)是合法的,p3 是一个常量指针,p3 的值永不改变,即 p3 永远指向变量 i2;同时 p3 指向的是常量,即我们不能通过 p3 改变所指对象的值。
(e)是合法的,p1 指向一个常量,即我们不能通过 p1 改变所指对象的值。
(f)是非法的,引用本身不是对象,因此不能让引用恒定不变。
(g)是合法的,i2 是一个常量,r 是一个常量引用。
练习 2.28
(a)是非法的,cp 是一个常量指针,因其值不能被改变,所以必须初始化。
(b)是非法的,cp2 是一个常量指针,因其值不能被改变,所以必须初始化。
(c)是非法的,ic 是一个常量,因其值不能被改变,所以必须初始化。
(d)是非法的,p3 是一个常量指针,因其值不能被改变,所以必须初始化;同时p3 指向的是常量,即我们不能通过 p3 改变所指对象的值。
(e)是合法的,但是 p 没有指向任何实际的对象。
练习 2.29
(a)是合法的,常量 ic 的值赋给了非常量 i。
(b)是非法的,普通指针 p1 指向了一个常量,从语法上说,p1 的值可以随意改变,显然是不合理的。
(c)是非法的,普通指针 p1 指向了一个常量,错误情况与上一条类似。
(d)是非法的,p3 是一个常量指针,不能被赋值。
(e)是非法的,p2 是一个常量指针,不能被赋值。
(f)是非法的,ic 是一个常量,不能被赋值。
练习 2.30
v2 是顶层 const,表示一个整型常量;p2 和r2 是底层 const,分别表示它们所指(所引用)的对象是常量。
p3 既是顶层const又是底层const,p3 是一个常量指针,指向一个整形常量。
练习 2.31
在执行拷贝操作时,顶层 const 和底层 const 区别明显。其中,顶层 const不受影响,这是因为拷贝操作并不会改变被拷贝对象的值。底层 const 的限制则不容忽视,拷入和拷出的对象必须具有相同的底层 const 资格,或者两个对象的数据类型必须能够转换。一般来说,非常量可以转换成常量,反之则不行。
r1=v2;是合法的,r1 是一个非常量引用,v2 是一个常量(顶层 const),把 v2的值拷贝给 r1 不会对 v2 有任何影响。
p1=p2;是非法的,p1 是普通指针,指向的对象可以是任意值,p2 是指向常量的指针(底层 const),令 p1 指向 p2 所指的内容,有可能错误地改变常量的值。
p2=p1;是合法的,与上一条语句相反,p2 可以指向一个非常量,只不过我们
不会通过 p2 更改它所指的值。
p1=p3;是非法的,p3 包含底层 const 定义(p3 所指的对象是常量),不能把p3 的值赋给普通指针。
p2=p3;是合法的,p2 和 p3 包含相同的底层 const,p3 的顶层 const 则可以忽略不计。
练习 2.32
上述代码是非法的,null 是一个 int 变量,p 是一个 int 指针,二者不能直接绑定。仅从语法角度来说,可以将代码修改为:
int null = 0, *p = &null;
显然,这种改法与代码的原意不一定相符。另一种改法是使用 nullptr:
int null = 0, *p = nullptr;
2.5 节练习
练习 2.33
前 3 条赋值语句是合法的,原因如下:
r 是 i 的别名,而 i 是一个整数,所以 a 的类型推断结果是一个整数;
ci 是一个整型常量,在类型推断时顶层 const 被忽略掉了,所以 b 是一个整数;
cr 是 ci的别名,而 ci 是一个整型常量,所以 c 的类型推断结果是一个整数。
因为 a、b、c都是整数,所以为其赋值 42 是合法的。
后 3 条赋值语句是非法的,原因如下:
i 是一个整数,&i 是 i 的地址,所以 d 的类型推断结果是一个整型指针;
ci是一个整型常量,&ci 是一个整型常量的地址,所以 e 的类型推断结果是一个指向整型常量的指针;
ci 是一个整型常量,所以 g 的类型推断结果是一个整型常量引用。
因为 d 和 e 都是指针,所以不能直接用字面值常量为其赋值;g 绑定到了整型常量,所以不能修改它的值。
练习 2.34
Visual Studio居然自己就把下面的d,e,g的错误报出来了,而且还有解释
比如:
练习 2.35
i 是一个整型常量,
j 的类型推断结果是整数,
k 的类型推断结果是整型常量
p 的类型推断结果是指向整型常量的指针
j2 的类型推断结果是整形常量,
k2 的类型推断结果是整形常量引用。
这个程序的输出的类型不太全面,有些没有把const输出出来。
可以通过把鼠标放到对应变量上查看该变量类型。
练习 2.36
在本题的程序中,初始情况下 a 的值是 3、b 的值是 4。
decltype(a) c=a;
使用的是一个不加括号的变量,因此 c 的类型就是 a 的类型,即该语句等同于 int c=a;
,此时 c 是一个新整型变量,值为 3。
decltype((b)) d=a;
使用的是一个加了括号的变量,因此 d 的类型是引用,即该语句等同于 int &d=a;
,此时 d 是变量 a 的别名。
执行++c
; ++d
;时,变量 c 的值自增为 4,因为 d 是 a 的别名,所以 d 自增 1意味着 a 的值变成了 4。
当程序结束时,a、b、c、d 的值都是 4。
附上书上原话:
练习 2.37
根据 decltype 的上述性质可知,c 的类型是 int,值为 3;
表达式 a=b 作为decltype 的参数,编译器分析表达式并得到它的类型作为 d 的推断类型,但是不实际计算该表达式,所以 a 的值不发生改变,仍然是 3;
d 的类型是 int&,d 是 a的别名,值是 3;
b 的值一直没有发生改变,为 4。
练习 2.38
auto 和 decltype 的区别主要有三个方面:
-
第一,auto类型说明符用编译器计算变量的初始值来推断其类型,而decltype虽然也让编译器分析表达式并得到它的类型,但是不实际计算表达式的值。
-
第二,编译器推断出来的 auto 类型有时候和初始值的类型并不完全一样,编译器会适当地改变结果类型使其更符合初始化规则。例如,auto 一般会忽略掉顶层const,而把底层const保留下来。与之相反,decltype会保留变量的顶层const。
-
第三,与 auto 不同,decltype 的结果类型与表达式形式密切相关,如果变量名加上了一对括号,则得到的类型与不加括号时会有不同。如果 decltype 使用的是一个不加括号的变量,则得到的结果就是该变量的类型;如果给变量加上了一层或多层括号,则编译器将推断得到引用类型。
举个例子来说明:
对于第一组类型推断来说,a 是一个非常量整数,c1 的推断结果是整数,c2 的推断结果也是整数,c3 的推断结果由于变量 a 额外加了一对括号所以是整数引用。
c1、c2、c3 依次执行自增操作,因为 c3 是变量 a 的别名,所以 c3 自增等同于 a自增,最终 a、c1、c2、c3 的值都变为 4。
对于第二组类型推断来说,d 是一个常量整数,含有顶层 const,使用 auto推断类型自动忽略掉顶层 const,因此 f1 的推断结果是整数;decltype 则保留顶层 const,所以 f2 的推断结果是整数常量。f1 可以正常执行自增操作,而常量f2 的值不能被改变,所以无法自增。
2.6 节练习
练习 2.39
该程序无法编译通过,原因是缺少了一个分号。因为类体后面可以紧跟变量名
以示对该类型对象的定义,所以在类体右侧表示结束的花括号之后必须写一个分号。
稍作修改,该程序就可以编译通过了。
struct Foo { /* 此处为空 */ };
int main ()
{
return 0;
}
尝试了下,报错情况如下:
在Visual Studio中:
在Dev-C++中:
练习 2.40
原书中的程序包含 3 个数据成员,分别是 bookNo(书籍编号)、units_sold(销售量)、revenue(销售收入),
新设计的 Sales_data 类细化了销售收入的计算方式,在保留 bookNo 和 units_sold 的基础上,新增了sellingprice(零售价、原价)、saleprice(实售价、折扣价)、discount(折扣),其中discount=saleprice/sellingprice。
struct Sales_data {
std::string bookNo; // 书籍编号
unsigned units_sold = 0; // 销售量
double sellingprice = 0.0; // 零售价
double saleprice = 0.0; // 实售价
double discount = 0.0 // 折扣
};
练习 2.41
不太确定
练习 2.42
不太确定
标签:常量,int,练习,整型,类型,第二章,指针 From: https://www.cnblogs.com/hisun9/p/18534594