C++ Primer 习题(第二章附答案)

练习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 下面哪个定义是不合法的,为什么?

1int ival = 1.01;     // 合法2int &rval1 = 1.01;   //非法,引用必须指向一个实际存在的对象而非字面值常量。3int &rval2 = ival;   //合法4int &rval3;      //非法,引用必须被初始化。

练习2.16 查看下面的所有赋值然后回答:那些赋值是不合法的?为什么?那些赋值是合法的?它们执行了什么样的操作?

int i = 0, &rl = i;
double d = 0, &r2 = d;
​
(1)r2 = 3.14159; //合法,3.14159赋给了变量d2)r2 = r1; //合法,i值赋给了d3)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;  
(1double* dp = &i;     //非法,dp是double指针,i是int变量,类型不匹配
(2int* ip = i;     //非法,不能直接把int变量赋给int指针
(3int* 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 下面那些句子是合法的?如果有不合法的句子,请说明为什么?

1const int buf;       // 不合法,声明一个const常量的同时必须初始化2int cnt = 0;     // 合法,声明并初始化一个int变量3const 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 略

你可能感兴趣的:(C++,c++)