练习2.1 类型int、long、long long 和short 的区别是什么?无符号类型和带符号类型的区别是什么?float 和double的区别是什么?
在C++语言中,int、long、long long 和short都属于整型,区别是C++标准规定的尺寸的最小值不同。其中,short 是短整型,占16位;int是整型,占16位;long 和long long均为长整型,均为长整形,分别占32位和64位。
大多数整型都可以划分为无符号类型和带符号类型,在无符号类型中所有比特都用来存储数值,但是仅能表示大于等于0的值;带符号类型则可以表示正数、负数或0。
float 和 double 分别是单精度浮点数和双精度浮点数,区别主要是在内存中所占的比特数不同,以及默认规定的有效位数不同。
练习2.2 计算按揭贷款时,对于利率、本金和付款分别应选择何种数据类型?说明你的理由。
在实际应用中,利率、本金和付款既有可能是整数,也有可能是普通的实数。因此应该选择一种浮点类型来表示。在三种可供选择的浮点类型 float、double和long double 中,double和float的计算代价比较接近且表示范围更广,long double 的计算代价比较大,一般情况下没有选择的必要。综上,选择double是比较恰当的。
练习2.3 读程序,写结果
unsigned u = 10, u2 = 42;
std::cout << u2 - u << std::endl; //32
std::cout << u - u2 << std::endl; // 表示无符号整型自动加模 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.4 略
练习2.5 指出下述字面值的数据类型并说明每一组内几种字面值的区别
(a)'a'<字符字面值>, L'a'<宽字符字面值,类型是wchar_t>, "a"<字符串字面值>, L"a"<宽字符字符串字面值
(b)10<十进制>, 10u<无符号整型>, 10L<长整型>, 10uL<无符号长整型>, 012<八进制>, 0xC<十六进制
(c)3.14,<浮点数> 3.14f<单精度浮点型字面值,类型是float>, 3.14L<扩展精度浮点型字面值,类型是long double>
(4)10<十进制>, 10u<无符号整型>, 10.<浮点型>, 10e-2<浮点型字面值>
练习2.6 下面两组定义是否有区别,如果有,请叙述之
int month = 9, day = 7; //定义正确,定义了两个十进制数9和7。
int month = 09, day = 07; // 定义错误,因为以0开头的数是八进制数,而数字9显然超出了八进制数能表示的范围。
练习2.7 下述字面值表示何种含义?它们各自的数据类型是什么?
(a)"who goes with F\145rgus?\012"
// \145表示字符“e”, \012表示换行符。 输出:who goes with Fergus?
(2) 3.14e1L // 科学计数法表示的扩展精度浮点数
(3) 1024f // 试图表示单精度浮点数,改写为1024.f
(4) 3.14L // 表示扩展精度浮点数,类型是long double
练习2.8 使用转义字符写一段程序,要求先输出2M,然后转到新的一行。修改程序使其先输出2, 然后输出制表符,再输出M,最后转到新的一行。
#include
using namespace std;
int main()
{
cout << "2\x4d\012";
cout << "2\tM\n";
return 0;
}
练习2.9 解释下列定义的含义。对于非法的定义,请说明错在何处并将其改正。
(a)std::cin >> int input_value // 错误
// int input_value
//std::cin >> input_value
(b)int i = { 3.14 }; // 引发警告,该语句定义了一个整型变量i,但是试图通过列表初始化的方式把浮点数3.14赋值给i,这样做将造成小数部分丢失,是一种不被建议的窄化操作。
(c)double salary = wage = 9999.99; //错误
//double salary,wage
//salary = wage = 9999.99
(d)int i = 3.14; // 引发警告,该语句定义了一个整型变量i,但是试图把浮点数3.14赋值给i,这样做将造成小数部分丢失,是一种不被建议的窄化操作
练习2.10 下列变量的初值分别是什么?
std::string g_str; //初始化为空串
int global_int; // 初始化为0
int main ()
{
int local_int; //不被初始化
std::string local_str; //初始化为空串
}
练习2.11 指出下面的语句是被声明还是定义。
(a)extern int ix = 1024; // 定义了变量ix
(b)int iy; // 声明并定义了变量iy
(c)extern int iz; // 声明了变量iz
练习2.12 请指出下面的名字中哪个是非法的?
(a)int doube = 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; //c++允许内层作用域重新定义外层作用域中已有的名字
int j = i; //j =100 。
}
练习2.14 下面的程序合法吗?如果合法,它将输出什么?
int i =100, sum =0;
for (int i =0; i != 10; ++i) {
sum += i;
}
cout << i << " " << sum << endl;
// i = 100, sum = 45,for循环内部i被重新定义,循环实际上是从i=0到i=9,因此输出的是全局变量i= 100, 局部变量sum依旧在作用域内,因此输出局部变量sum=45。
练习2.15 下面哪个定义是不合法的,为什么?
(1)int ival = 1.01; // 合法
(2)int &rval1 = 1.01; //非法,引用必须指向一个实际存在的对象而非字面值常量。
(3)int &rval2 = ival; //合法
(4)int &rval3; //非法,引用必须被初始化。
练习2.16 查看下面的所有赋值然后回答:那些赋值是不合法的?为什么?那些赋值是合法的?它们执行了什么样的操作?
int i = 0, &rl = i;
double d = 0, &r2 = d;
(1)r2 = 3.14159; //合法,3.14159赋给了变量d
(2)r2 = r1; //合法,i值赋给了d
(3)i = r2; //合法,d的值赋给了变量i,窄化操作
(4)r1 = d; //合法,d的值赋给了变量i,窄化操作
练习2.17 执行下面的代码段将会输出什么结果?
int i, &ri = i; //ri实际上是i的别名
i = 5;
ri = 10;
cout << "i = " << i << " ri = " << ri << endl;
//i = 10, ri = 10
//改变ri的值也会改变i的值
练习2.18 编写代码分别更改指针的值以及指针所指对象的值。
#include
using namespace std;
int main()
{
int i = 5, j = 10; //定义整型变量i,j
int* p = &i; //定义整型指针p
cout << p << " " << *p << endl;
p = &j; //更改指针值
cout << p << " " << *p << endl;
*p = 20; //更改指针所指对象值
cout << p << " " << *p << endl;
j = 30; //更改指针所指对象值
cout << p << " " << *p << endl;
return 0;
}
练习2.19 说明指针和引用的主要区别。
指针“指向”内存中的某个对象,而引用“绑定到”内存中某个对象,它们都实现了对其他对象的间接访问,二者的区别主要有两方面:
第一,指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内可以指向几个不同的对象;引用不是一个对象,无法令引用重新绑定到另外一个对象。
第二,指针无须在定义时赋初值,和其他内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值;引用则必须在定义时赋初值。
练习2.20 请叙述下面这段代码的作用。
int i = 42; //定义一个整型变量i
int *p1 = &i; //定义一个整型指针p1,指向变量i
*p = *p1**p1; //取出p1所指的当前值,计算平方后重新赋给p1所指的变量i
练习2.21 请解释下述定义。在这些定义中有非法的吗?如果有,为什么?
int i = 0;
(1)double* dp = &i; //非法,dp是double指针,i是int变量,类型不匹配
(2)int* ip = i; //非法,不能直接把int变量赋给int指针
(3)int* p = &i; //合法
练习2.22 假设p是一个int型指针,说明下述代码的含义。
if (p) //检验指针所指的地址值
if (*p) //检验指针所指的对象内容
练习2.23 给定指针p,你能知道它是否指向了一个合法的对象?如果能,叙述判断的思路;如果不能,说明原因。
在C++程序中,应该尽量初始化所有指针,并且尽可能等定义了对象之后在定义指向它的指针。
如果实在不清楚指针应该指向何处,就把它初始化为nullptr或者0,这样程序就能检测并知道它
有没有指向一个具体的对象了。在此前提下,判断p是否指向合法对象,只需把p作为if语句的条
件即可,如果p的值是nullptr,则条件为假;反之,条件为真。
如果不注意初始化所有指针而贸然判断指针的值,则有可能引发不可预知的结果。一种处理方
发是把if(p)置于try结构中,当程序块顺利执行,表示p指向了合法的对象;当程序块出错跳转
到catch语句中,表示p没有指向合法的对象。
练习2.24 在下面这段代码中为什么p合法而lp非法?
int i = 42;
void *p = &i; //因为void*是一种特殊的指针类型,可以存放任意对象的地址
long *lp = &i; //lp是一个长整型指针,而i只是一个普通整型数,二者类型不匹配
练习2.25 说明下列变量的类型和值
(1) int* ip, i, &r = i;
//ip是一个整型指针,指向一个整数型,值为整型数在内存中的地址
//i是一个整型数;r是一个引用,它绑定了i,r的值就是i的值
(2) int i, *p =0;
// i是一个整型数, ip是一个整型指针,但是不指向任何具体的对象,它的初始化为0
(3) int* ip, ip2;
// ip是一个整型指针,指向一个整数型,值为整型数在内存中的地址;ip2是一个整型数
练习2.26 下面那些句子是合法的?如果有不合法的句子,请说明为什么?
(1)const int buf; // 不合法,声明一个const常量的同时必须初始化
(2)int cnt = 0; // 合法,声明并初始化一个int变量
(3)const int sz = cnt; // 合法,声明一个int const常量,并初始化。
(4)++ cnt; ++sz; // 不合法,sz为常量,不能进行++操作。
练习2.27 下面哪些初始化是合法的,说明原因。
(a)int i = -1, &r = 0; // 不合法, 非常量引用r不能引用字面值常量0
(b)int *const p2 = &i2; // 合法,p2是一个常量指针,p2的值永不改变,即p2永远指向变量i2
(c)const int i = -1, &r = 0; // 合法, i是一个常量,r是一个常量引用
(d)const int* const p3 = &i2; // 合法,p3是一个常量指针,p3的值永不改变,即p3永远指向变量i2;同时p3指向的是常量,即我们不能通过p3改变所指对象值
(e)const int* p1 = &i2; // 合法,p1指向一个常量,即我们不能通过p1改变所指对象值
(f)const int& const r2; // 不合法,引用本身不是对象,因此不能让引用恒定不变
(g)const int i2 = i, &r = i; // 合法,i2是一个常量,r是一个常量引用
const修饰指针总结
const int *p = &a //常量指针,指针的指向可以修改,但是指针指向的值不可以修改
int *const p = &a //指针常量,指针的指向不可以修改,但是指针指向的值可以修改
const int *const p = &a //const即修饰指针又修饰常量,指针指向和指向的值都不可以修改
练习 2.28 说明下面的这些定义是什么意思,挑出其中不合法的。
(a) int i, *const cp; // 不合法,cp是一个常量指针,因其值不能改变,所以必须初始化。
(b) int *p1, *const p2; // 不合法,同上
(c) const int ic, &r = ic; // 不合法,ic是一个常量,因其值不能改变,所以必须初始化。
(d) const int *const p3; // 不合法,p3是一个常量指针,因其值不能改变,所以必须初始化。
(e) const int *p; // 合法,但是p没有指向任何实际的对象
练习 2.29 假设已有上一个练习定义的那些变量,则下面的那些语句是合法的?请说明原因。
(a) i = ic; // 合法,常量ic的值赋给了非常量i
(b) p1 = p3; // 不合法,普通指针p1指向了一个常量,从语法上说,p1的值可以随意修改
(c) p1 = &ic // 不合法,同上
(d) p3 = &ic // 不合法,p3是一个常量指针,不能被赋值
(e) p2 = p1; // 不合法,同上
(f) ic = *p3; // 不合法,ic是常量,不能被赋值
练习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和p3是顶层const,分别表示一个整型常量和一个整型常量指针;
//p2和r2是底层const,分别表示它们所指的(所引用)的对象是常量
练习2.31 假设已有上一个练习中所做的那些声明,则下面的那些语句是合法的?请说明顶层const和底层const在每个例子中有何体现。
r1 = v2;
//r1是一个非常量引用,v2是一个常量(顶层),把v2的值拷贝给r1不会对v2有任何影响。
p1 = p2; p2 = p1;
//p1=p2是非法的,p1是普通指针,指向对象可以是任意值,p2是指向常量的指针(底层),令p1指向p2所指的内容,有可能错误的改变常量的值
//p2=p1是合法的,p2可以指向一个非常量,只不过我们不会通过p2更改它所指的值
p1 = p3; p2 = p3;
//p1=p3是非法的,p3包含底层const定义(p3所指的对象是常量),不能把p3的值赋给普通指针
//p2=p3是合法的,p2和p3包含相同的底层const,p3的顶层const则可以忽略不计
练习2.32 下面的代码是否合法?如果非法,请设法将其修改正确
int unll = 0,*p = null;
// int null =0,*p = &null;
// int null = 0,*p = nullptr;
练习2.33 利用本节定义的变量,判断下列语句的运行结果
a = 42;b = 42; c = 42; //合法
d = 42;e = 42; g = 42; //不合法
练习2.34 基于上一个练习中的变量和语句编写一段程序,输出赋值前后变量的内容,你刚才的推断真确吗?如果不对,请反复研读本节的实例直到你明白错在何处为止。
#include
using namespace std;
int main()
{
int i = 0, & r = i;
auto a = r; //a是一个整数(r是i的别名,而i是个整数)
const int ci = i, & cr = ci;
auto b = ci;//b是一个整数(ci的顶层const特性被忽略了)
auto c = cr;//c是一个整数(cr是ci的别名,ci本身是一个顶层const)
auto d = &i;//d是一个整型指针(整数的地址就是指向整数的指针)
auto e = &ci;//e是一个指向整型常量的指针(对常量对象取地址是一种底层const)
auto& g = ci;//g是一个整型常量的引用,绑定到ci
cout << a << " " << b << " " << c << " " << d << " " << e << " " << g << endl;
a = 42; b = 42; c = 42;
d = 42; //不能将int类型的值分配到int*类型的实体
e = 42; //不能将int类型的值分配到int*类型的实体
g = 42; //表达式必须是可修改的左值
cout << a << " " << b << " " << c << " " << d << " " << e << " " << g << endl;
}
练习2.35 判断下列定义推断出的类型是什么,然后编写程序进行验证。
#include
#include
using namespace std;
int main()
{
const int i = 42;
auto j = i;
const auto& k = i;
auto* p = &i;
const auto j2 = i, & k2 = i;
cout << typeid(i).name() << endl;
cout << typeid(j).name() << endl;
cout << typeid(k).name() << endl;
cout << typeid(p).name() << endl;
cout << typeid(j2).name() << endl;
cout << typeid(k2).name() << endl;
return 0;
}
练习2.36 关于下面的代码,请指出每一个变量的类型以及程序结束时它们各自的值
#include
using namespace std;
int main()
{
int a = 3, b = 4;
decltype(a) c = a; // 使用不加括号变量相当于 int c = a;
decltype((b)) d = a; // 使用加括号变量相当于 int &d = a;
++c; // ++c之后的值为4;
++d; // ++d之后的值为4;
cout << a << " " << b << " " << c << " "<< d << endl;
return 0;
}
练习 2.37 赋值是会产生引用的一类典型表达式,引用的类型就是左值的类型。也就是说,如果i是int,则表达式i=x的类型是int&。根据这一特点,请指出下面的代码中每一个变量的类型和值。
#include
#include
using namespace std;
int main()
{
int a = 3, b = 4;
decltype(a) c = a; // 使用不加括号变量相当于 int c = a;
decltype(a=b) d = a; // 分析表达式并得到它的类型作为d的推断类型,但是不实际计算该表达式 相当于 int &d = a;
cout << a << " " << b << " " << c << " "<< d << endl;
return 0;
}
练习2.38 说明由decltype指定类型和由auto指定类型有何区别。请举出一个例子,decltype指定的类型与auto指定的类型一样;在举一个例子,decltype指定的类型与auto指定的类型不一样
//第一,auto类型说明符用编译器计算的初始值来判断其类型,而decltype虽然也让编译器分析表达式并得到它的类型,但是不实际计算表达式的值。
//编译器推断出来的auto类型有时候和初始值类型并不完全一样,编译器会适当的改变结果类型使其更符合初始化规则。
//与auto不同,decltype的结果类型与表达式形式密切相关,如果变量名加上了一对括号,则得到的类型与不加括号时会有不同
#include
#include
using namespace std;
int main()
{
int a = 3;
auto c1 = a;
decltype(a)c2 = a;
decltype((a))c3 = a;
const int d = 5;
auto f1 = d;
decltype(d)f2 = d;
cout << typeid(c1).name() << endl;
cout << typeid(c2).name() << endl;
cout << typeid(c3).name() << endl;
cout << typeid(f1).name() << endl;
cout << typeid(f2).name() << endl;
c1++;
c2++;
c3++;
f1++;
f2++; //错误:f2是整型常量,不能执行自增操作
cout << a << " " << c1 << " " << c2 << " " << c3 << " " << f1 << " " << f2 << endl;
return 0;
}
练习2.39 编译下面的程序观察其运行结果,注意,如果忘记写类定义体后面的分号会发生什么情况?记录下相关信息,以后可能会有用。
#include
using namespace std;
struct Foo {/*this is nothing */ }; //注意:类体右侧表示结束的花括号之后必须写一个分号
int main()
{
return 0;
}
练习 2.40 根据自己的理解写出Sales_data类,最好与书中的例子有所区别。
#include
using namespace std;
struct Sales_data {
string bookNo; //书籍编号
unsigned units_sold = 0; //销售量
double sellingprice = 0.0; //零售价
double saleprice = 0.0; //实售价
double discount = 0.0; //折扣
};
练习2.41 -2.42 略