练习2.1
类型 int、long、long long 和 short 的区别是什么?无符号类型和带符号类型的区别是什么?float 和 double的区别是什么?
short和int至少16位,long至少16位,long long至少32位。
有符号可以表示正数、负数和零,无符号只能表示不小于零的数。
float是单精度浮点数,有6位有效数字,double是双精度浮点数,有10位有效数字。
用法:
使用整数运算。short通常太小,实际上long通常与int的大小相同,如果数据值大于int的最小保证大小,那么就使用long long。(简而言之:short < int < long < long long)
当知道值不能为负时,使用无符号类型。(一句话:没有否定,没有署名。)
使用双精度浮点计算;float通常没有足够的精度,而且双精度计算与单精度计算的成本可以忽略不计。事实上,在某些机器上,双精度操作比单精度操作要快。long double所提供的精度通常是不必要的,而且常常需要相当大的运行时成本。(简而言之:float < double < long double)
练习2.2
计算按揭贷款时,对于利率、本金和付款分别应选择何种数据类型?说明你的理由。
使用double或者float。
练习2.3
读程序写结果。
当一个算数表达式中既有无符号数又有int值时,那个int值就会转换成无符号数。
unsigned u = 10, u2 = 42;
std::cout << u2 - u << std::endl; // 结果为32
std::cout << u - u2 << std::endl; // 结果为4294967264,因为10-42应该是-32,在计算机中用二进制存储
// 第一位是1表示负数,但由于是无符号型,二进制第一位变成0,即为4294967264
int i = 10, i2 = 42;
std::cout << i2 - i << std::endl; // 结果为32
std::cout << i - i2 << std::endl; // 结果为-32
std::cout << i - u << std::endl; // 结果为0
std::cout << u - i << std::endl; // 结果为0
练习2.5
指出下述字面值的数据类型并说明每一组内几种字面值的区别:
前缀: u unicode16字符 char16_t
U unicode32字符 char32_t
L 宽字符 wchar_t
u8 utf-8 char
后缀:u或U 表示该字面值的最小匹配类型为无符号类型
l或L 表示该字面值的最小匹配类型为long
ll或LL 表示该字面值的最小匹配类型为long long
f或F 表示该字面值最小匹配类型为float类型
l或L 表示该字面值最小匹配类型为long double
(a) 'a', L'a', "a", L"a" // 字符、宽字符、字符串、字符串宽字符
(b) 10, 10u, 10L, 10uL, 012, 0xC* // 十进制、无符号十进制、长十进制、无符号长十进制、八进制、十六进制
(c) 3.14, 3.14f, 3.14L // double、float、long double
(d) 10, 10u, 10., 10e-2 // 十进制、无符号十进制、double、double
练习2.6
下面两组定义是否有区别,如果有,请叙述之:
第一行的整数定义的是十进制的数
第二行的定义是不合法的,因为0开头的是八进制,但是八进制中是没有9的。
练习2.7
下述字面值表示何种含义?它们各自的数据类型是什么?
(a): Who goes with Fergus?(new line) // string
(b): 31.4 // long double
(c): 1024 // float
(d): 3.14 // long double
练习2.8
请利用转义序列编写一段程序,要求先输出 2M,然后转到新一行。修改程序使其先输出 2,然后输出制表符,再输出 M,最后转到新一行。
#include <iostream>
int main()
{
std::cout << "\062\115\012";
std::cout << "\062\t\115\012";
}
练习2.9
解释下列定义的含义,对于非法的定义,请说明错在何处并将其改正。
(a): 不合法,不能在输入时才定义,需在cin之前定义input_value。
int input_value = 0;
std::cin >> input_value;
(b):不合法,当我们使用列表初始化且初始值存在丢失信息的风险,编译器会报错,这里类型“double”不能缩小为“int”。
double i = { 3.14 };
(c):不合法,未声明wage就使用。
double wage;
double salary = wage = 9999.99;
(d):合法,但是值会被强制类型转换。
double i = 3.14;
练习2.10
下列变量的初值分别是什么?
std::string global_str;
int global_int;
int main()
{
int local_int;
std::string local_str;
}
1、定义在任何函数体外的变量会被初始化为0。
2、在函数体中定义的内置类型的未初始化对象具有未定义的值。没有显式初始化的类类型的对象有一个类定义的值。
global_str是全局变量,所以值是空字符串。
global_int是全局变量,所以值为0。
local_int是一个未初始化的局部变量,所以它有一个未定义的值。
local_str也是一个未初始化的局部变量,但是它有一个类定义的值。它是空字符串。
练习2.11
指出下面的语句是声明还是定义:
(a) extern int ix = 1024; // 定义
(b) int iy; // 定义
(c) extern int iz; // 声明
练习2.12
请指出下面的名字中哪些是非法的?
C++的标识符由字母、数字和下划线组成,其中必须以字母或下划线开头。同时,用户自定义的标识符不能连续出现两个下划线,也不能以下划线紧连大写字母开头。此外,定义在函数体外的标识符不能以下划线开头。
(a) int double = 3.14; // 不合法,使用了关键字double作为变量名
(b) int _; // 合法
(c) int catch-22; // 不合法,“-”无效字符
(d) int 1_or_2 = 1; // 不合法,不能以数字开头
(e) double Double = 3.14; // 合法
练习2.13
下面程序中 j 的值是多少?
int i = 42;
int main()
{
int i = 100;
int j = i;
}
新建局部变量会覆盖同名的全局变量,所以j的值为100。
练习2.14
下面的程序合法吗?如果合法,它将输出什么?
int i = 100, sum = 0;
for (int i = 0; i != 10; ++i)
sum += i;
std::cout << i << ” ” << sum << std::endl;
合法,输出:100 45
练习2.15
下面的哪个定义是不合法的?为什么?
引用是为对象起的另外一个名字,定义引用时,程序把引用和它的初始值绑定在一起,而不是把初始值拷贝给引用,并且无法令引用重新绑定到另外一个对象,所以引用必须初始化。
(a) int ival = 1.01; // 合法
(b) int &rval1 = 1.01; // 不合法,初始化必须是一个对象
(c) int &rval2 = ival; // 合法
(d) int &rval3; // 不合法,引用必须初始化
练习2.16
考察下面的所有赋值然后回答:哪些赋值是不合法的?为什么?哪些赋值是合法的?它们执行了哪些操作?
(a): 合法 令d = 3.14159
(b): 合法 强制类型转换
(c): 合法 强制类型转换
(d): 合法 强制类型转换
练习2.17
执行下面的代码段将输出什么结果?
int i, &ri = i;
i = 5; ri = 10;
std::cout << i << ” ” << ri << std::endl;
输出:10 10
练习2.18
编写代码分别改变指针的值以及指针所指对象的值。
int a = 0, b = 1;
int *p1 = &a, *p2 = p1;
// 改变指针的值
p1 = &b;
// 改变指针所指对象的值
*p2 = b;
练习2.19
说明指针和引用的主要区别
定义:指针是一个任何其他类型的“指向”,而引用是对象的“另一个名称”。
区别:
- 引用是已存在对象的另一个名称。指针本身就是一个对象。
- 一旦初始化,引用就绑定到它的初始对象上,无法重新绑定引用以引用不同的对象。而指针可以重新分配或拷贝。
- 引用必须初始化。指针在定义时可以不需要初始化。
- 引用不能为NULL,指针可以为NULL。
练习2.20
请叙述下面这段代码的作用。
int i = 42;
int *p1 = &i;
*p1 = *p1 * *p1;
p1指针指向i, i的值改为1764(42*42)
练习2.21
请解释下述定义。在这些定义中有非法的吗?如果有,为什么?
(a) double* dp = &i; // 不合法,不能用int *类型的值初始化double *类型的变量
(b) int *ip = i; // 不合法,不能用一个类型为int的值初始化一个类型为int *的变量
(c) int *p = &i; // 合法
练习2.22
假设 p 是一个 int 型指针,请说明下述代码的含义。
if (p) // 是否p是一个空指针
if (*p) // p所指向的值是否是0
练习2.23
给定指针 p,你能知道它是否指向了一个合法的对象吗?如果能,叙述判断的思路;如果不能,也请说明原因。
不可以,因为需要更多的信息来确定指针是否有效。
练习2.24
在下面这段代码中为什么 p 合法而 lp 非法?
int i = 42;
void *p = &i;
long *lp = &i;
因为void*类型的指针可以保存任何对象的地址。但是不能用int *类型的对象初始化long *类型的变量。
练习2.25
说明下列变量的类型和值。
(a) int* ip, i, &r = i; // ip是指向int的指针,i是一个整数,r是i的引用。
(b) int i, *ip = 0; // ip是一个空指针,i是一个整数。
(c) int* ip, ip2; // ip是一个指向int的指针,ip2是一个整数。
练习2.26
下面哪些语句是合法的?如果不合法,请说明为什么?
有关const的用法总结,我在另一篇博客
C++中Const的用法总结
中写了。
const int buf; // 不合法, const对象必须要初始化
int cnt = 0; // 合法
const int sz = cnt; // 合法
++cnt; // 合法
++sz; // 不合法, sz是一个const对象,不能被改变
练习2.27
下面的哪些初始化是合法的?请说明原因。
int i = -1, &r = 0; // 不合法, r必须引用一个对象,const int才可以引用常量
int *const p2 = &i2; // 合法,p2为一个常量指针,初始化为i2对象的地址
const int i = -1, &r = 0; // 合法,i是一个常量,r是一个常量引用
const int *const p3 = &i2; // 合法,p3是一个指向常量对象的常量的引用
const int *p1 = &i2; // 合法,p1是一个指向常量对象的指针
const int &const r2; // 不合法, r2是一个引用,不能const
const int i2 = i, &r = i; // 合法,i2是一个常量,r是一个常量引用,不能够通过r修改i的值
练习2.28
说明下面的这些定义是什么意思,挑出其中不合法的。
int i, *const cp; // 不合法,常量指针cp必须初始化
const int ic, &r = ic; // 不合法,ic必须初始化
int *p1, *const p2; // 不合法,常量指针p2必须初始化
const int *const p3; // 不合法,常量指针p3必须初始化
const int *p; // 合法,p是一个指向常量对象的指针
练习2.29
假设已有上一个练习中定义的那些变量,则下面的哪些语句是合法的?请说明原因。
i = ic; // 合法
p1 = p3; // 不合法, p3是一个指向常量对象的指针
p1 = ⁣ // 不合法, ic是一个const int,p1是一个int *
p3 = ⁣ // 不合法, p3是一个常量指针
p2 = p1; // 不合法, p2是一个常量指针
ic = *p3; // 不合法, ic是一个const int.
练习2.30
对于下面的这些语句,请说明对象被声明成了顶层const还是底层const?
const int v2 = 0; int v1 = v2;
int *p1 = &v1, &r1 = v1;
const int *p2 = &v2, *const p3 = &i, &r2 = v2;
v2是顶层const, p2是底层const。
p3:最右边是顶层const,最左边是底层const。
r2是底层const。
练习2.31
假设已有上一个练习中所做的那些声明,则下面的哪些语句是合法的?请说明顶层const和底层const在每个例子中有何体现。
r1 = v2; // 合法,v2的顶层const可以被忽略
p1 = p2; // 不合法,p2有一个const,但是p1没有
p2 = p1; // 合法,可以从int *转换成const int *.
p1 = p3; // 不合法,p3有底层const,但是p1没有
p2 = p3; // 合法,p2具有与p3相同的底层const
练习2.32
下面的代码是否合法?如果非法,请设法将其修改正确。
int null = 0, *p = null;
不合法
int null = 0, *p = nullptr;
练习2.33
利用本节定义的变量,判断下列语句的运行结果。
auto会自动忽略掉顶层const,而底层const会被保留。
auto定义的变量必须有初始值。
a = 42; // 把42赋给int a.
b = 42; // 把42赋给int b.
c = 42; // 把42赋给int c.
d = 42; // 错误, d是一个int *,应该是:*d = 42;
e = 42; // 错误, e是一个const int *,应该是:e = &c;
g = 42; // 错误, g是一个绑定到ci上的const int&
练习2.35
判断下列定义推断出的类型是什么,然后编写程序进行验证。
const int i = 42; // i是const int.
auto j = i; // j是int
const auto &k = i; // k是const int&
auto *p = &i; // ps是const int *
const auto j2 = i, &k2 = i; // j2是const int,k2是const int&
练习2.36
关于下面的代码,请指出每一个变量的类型以及程序结束时它们各自的值。
decltype((variable))双层括号的结果永远是引用,decltype(variable)单层括号的结果只有当variable本身就是一个引用时才是引用。
int a = 3, b = 4;
decltype(a) c = a; // c是int类型
decltype((b)) d = a; // d是a的引用
++c; // c的值是4
++d; // d的值是4
练习2.37
赋值是会产生引用的一类典型表达式,引用的类型就是左值的类型。也就是说,如果 i 是 int,则表达式 i=x 的类型是 int&。根据这一特点,请指出下面的代码中每一个变量的类型和值。
当decltype()的括号中是一个表达式时,如果表达式的结果对象能作为一条赋值语句的左值,将返回一个引用类型。
int a = 3, b = 4;
decltype(a) c = a; // c是一个int类型
decltype(a = b) d = a; // d是一个a的引用,这里编译器只分析表达式的类型,并不实际执行表达式,所以a的值不是4
// 值: a = 3, b = 4, c = 3, d = 3
练习2.38
说明由decltype 指定类型和由auto指定类型有何区别。请举一个例子,decltype指定的类型与auto指定的类型一样;再举一个例子,decltype指定的类型与auto指定的类型不一样。
如果使用引用类型,auto会识别为其所指对象的类型,decltype则会识别为引用的类型。
decltype的结果类型与表达式形式密切相关。
int i = 0, &r = i;
// 相同
auto a = i;
decltype(i) b = i;
// 不相同
auto c = r;
decltype(r) d = r;
练习2.39
编译下面的程序观察其运行结果,注意,如果忘记写类定义体后面的分号会发生什么情况?记录下相关的信息,以后可能会有用。
struct Foo { /* 此处为空 */ } // 注意:没有分号
int main()
{
return 0;
}
会报错 Error message:
[Error] expected ';' after struct definition