练习2.1:类型int、long、long long和short的区别是什么?无符号类型和带符号类型的区别是什么?float和double的区别是什么?
答:
①int、long、long long和short都属于整型。区别是类型在内存中所占的最小 比特数不同。
short 16位,int 16位, long 32位, long long 64位。
②无符号型中所有的比特数都用来存储数值,仅能表示大于等于0的值。
带符号型可表示正数 负数或 0.
③float和double分别是单精度浮点数和双精度浮点数。区别主要是在内存中所占的位数不同,还有,它们的有效位数不同。
练习2.2:计算按揭贷款时,对于利率 本金 和付款分别应选择何种数据类型?
double。
练习2.3: 读程序写结果.
#include
/*统计输入流中每个值连续出现出现了多少次*/
int main()
{
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 <
这里解释一下第二个输出:
首先这里的unsigned其实就是unsigned int. 总共有 32 位。由于类型是无符号的,所以负值会被转化:
4294967264 = (-32(计算结果)+ 2的32次方)% 2的32次方。
无符号型的超出范围的转换都可以用上面的思路计算。
练习2.4:编写程序检查你的估计是否正确。
正确。
练习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 单精度浮点, 3.14L long double型的扩展精度浮点数。
(d)10 普通整型, 10u无符号整型, 10. 浮点数, 10e-2 科学计数法表示的浮点数(10*10^-2)
练习2.6:下面两组定义是否有区别?
有区别。
第一行赋值十进制数。
第二行赋值八进制数。 但是int month = 09;无法通过编译,因为9超出八进制表示范围。
练习2.7:下面的字面值表示何种含义?他们各自的数据类型是什么?
(a)"Who goes with F\145rgus?\012"
含义:Who goed with Fergus?(换行符)
(b)科学计数法表示的扩展精度浮点数。(3.14*10^1)
(c)错误。应该表示为 1024.f
(d)扩展精度的浮点数。
练习2.8:请利用转义序列编写一段程序,要求先输出2M,然后转到新一行。修改程序使其先输出2,然后输出制表符,再输出M,最后转到新一行。
#include
int main()
{
std::cout << "2\x4d\012";
std::cout << "2\t\115\n";
return 0;
}
(a)错误。应该改成:
int input_value;
std::cin >> input_value;
(b)int i = {3.14}; 引发警告,浮点数赋值给整型的i造成精度损失。
(c)错误。改成:
double salary,wage;
salary = wage = 9999.99;
(d)int i = 3.14;引发警告。理由同(b)
练习2.10:下列变量的初始值是什么?
global_str 、local_str都被默认初始化为空串。
global_int定义在所有函数体之外,被默认初始化为0;
local_int定义在main函数体之内,不被默认初始化,即local_int是未定义的。
练习2.11:指出下面的语句是声明还是定义:
(a)定义
(b)声明并且定义
(c)声明
重点:
声明和定义的区别实际上很重要。如果要在多个文件中使用同一个变量,就必须将声明和定义分离。此时,变量的定义出现在且只能出现在一个文件中,而其他用到该变量的文件必须对其进行声明,却绝对不能重复定义。
如果想声明一个变量而非定义它,就在变量名前面添加关键字extern,而且不要显式地初始化变量。
练习1.12:请指出下面的名字中哪些是非法的?
(a)非法 (b)合法
(c)非法 (d)非法
(e)合法
练习1.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:考察下面所有赋值然后回答:哪些赋值是不合法的?为什么?哪些赋值是合法?他们执行了什么样的操作?
都是合法的。(c)(d)有精度损失。窄化操作。
练习2.17:执行下面的代码段将输出什么结果。
int i, &ri = i;
i = 5; ri = 10;
std::cout << i << " " << ri << std::endl;
输出结果: 10 10
练习2.18:编写代码分别更改指针的值以及指针所指对象的值。
#include
int main()
{
int i = 5, j = 10;
int *p = &i;
std::cout << p << " " << *p << std::endl;
p = &j;
std::cout << p << " " << *p << std::endl;
*p = 20;
std::cout << p << " " << *p << std::endl;
return 0;
}
指针“指向”内存中的某个对象,而引用“绑定到”内存中的某个对象,他们都实现了对其他对象的间接访问,二者的区别主要有两方面:
第一,指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以指向几个不同的对象;引用不是一个对象,无法令引用重新绑定到另外一个对象。
第二,指针无需在定义时赋初值,和其他内置类型一样,在块作用域内定义的指针如果没有初始化,也将拥有一个不确定的值;引用则必须在定义时赋初值。
练习2.20:请叙述下面这段代码的作用。
int i = 42;
int *p = &i;
*p1 = *p1 * *p1;
通过指针把 变量 i 存储的值变为原来的平方。
练习2.21:请解释下述定义。在这些定义中有非法的吗?如果有,为什么?
int i = 0;
(a) double *dp = &i; //非法。dp是一个double指针,i是int型变量,类型不匹配。
(b) int *ip = i; //非法。不能把int型变量赋值给一个指针。
(c) int *p = &i; //合法。
练习2.22:假设p是一个int型指针,请说明下述代码的含义。
if( p ) // 当p是一个有效指针时条件为真。
if( *p ) // 当p所指的对象的值非0的时候,条件为真。
练习2.23:给定指针p,你能知道它是否指向了一个合法对象吗?如果能,叙述判断的思路;如果不能,也请说明原因。
如果定义指针时 都先初始化为nullptr,可以用if语句判断。
练习2.24:在下面这段代码中为什么p合法而lp非法?
int i = 42;
void *p = &i; //void*指针可以存放任意对象的地址。
long *lp = &i; //类型不匹配
练习2.25:说明下列变量的类型和值。
(a) int *ip, i, &r = i; // ip是指向int型变量的指针, i是int型变量,r是对i的引用。
(b) int i,*ip = 0; // i是int型变量, ip是指向int变量的指针。
练习2.26:下面那些句子是合法的?如果有不合法的句子,请说明为什么?
(a)const int buf; //非法。因为const对象在创建后其值就不能改变,所以const对象必须初始化。
(b)int cnt = 0; //合法。
(c)const int sz = cnt; //合法。
(d)++cnt; //合法。
++sz; //非法。sz对象是const对象,不能改变其值。
练习2.27:下面的哪些初始化时合法的?请说明原因。
(a) int i = -1, &r = 0; //非法。r是一个普通的引用,不能绑定到字面值常量值0。
(b) int *const p2 = &i2; //合法。p2是常量指针,永远指向变量i2。
(c)const int i = -1, &r = 0; //合法。此时的r是常量引用,可以绑定到字面值常量0上。
(d)const int *const p3 = &i2; //合法。p3是常量指针,永远指向i2。同时,p3是指向常量的指针,也就是说我们 //不能通过p3改变i2的值。
(e)const int *p1 = &i2; //合法。但不能通过p1改变i2的值。
(f) const int &const r2; //非法。引用本身不是对象,不能让引用恒定不变!!
(g)const int i2 = i, &r = i; //合法。i2是常量,r是常量引用。
练习2.28:说明下面这些定义是什么意思,挑出其中不合法的。
重要的事先重复三遍:
(a)int i, *const cp; // 非法。cp是常量指针,必须初始化。
(b) int *p1, *const p2; //非法。p2是常量指针,必须初始化。
(c) const int ic, &r = ic; //非法。ic是一个常量,必须初始化。
(d) const int *const p3; //非法。p3是常量指针,必须初始化。
(e) const int *p; //合法。但p没有指向实际对象。
练习2.29:假设已有上一个练习中定义的那些变量,则下面的哪些语句是合法的?请说明原因。
一定要注意:
const对象一定要初始化,不能对const对象赋值!!!
(a) i = ic; //合法。常量ic的值赋值给变量i。
(b) p1 = p3; //非法。普通指针p1指向了一个常量,能随意改变指向对象的值,所以非法。
(c) p1 = ⁣ //非法。普通指针p1不能指向一个const对象。
(d) p3 = ⁣ // 非法。p3是一个常量指针,不能对其赋值。(本就初始化了)
(e) p2 = p1; //非法。p2是一个常量指针,不能赋值。
(f) ic = *p3; //非法。 ic是一个常量,不能赋值。
练习2.30:对于下面的这些语句,请说明对象被声明成顶层const还是低沉const?
const int v2 = 0; //v2是顶层const
int v1 = v2; int *p1 = &v1, &r1 = v2;
const int *p2 = &v2, *const p3 = &i, &r2 = v2;
//p2是底层const,p3既是顶层const又是底层const,r2是顶层const。
练习2.31:假设已有上一个练习中所做的那些声明,则下面的哪些语句是合法的?请说明顶层const和底层const在每个例子中有何体现。
r1 = v2; //合法。
p1 = p2; // 非法。普通指针p1不能指向常量。
p2 = p1;//合法。
p1 = p3; //非法。普通指针p1不能指向常量。
p2 = p3; //合法。
练习2.32:下面的代码是否合法?如果非法,请设法将其修改正确。
int null = 0, *p = null;
非法。
不能将int值直接赋值给int型指针,故应该改成:
int null = 0, *p = &null;
练习2.33:利用本节定义的变量,判断下列语句的运行结果。
a = 42;//合法
b = 42;//合法
c = 42;//合法
d = 42;//非法。d的类型是一个整型指针。不能对其赋值常数。
e = 42; //非法。e的类型是一个指向整型常量的指针。
g = 42;//非法。g是一个整型常量的引用,不能赋值。
练习2.34:基于上一个练习中的变量和语句编写一段程序,输出赋值前后变量的内容,你刚才的推断正确吗?
正确。
练习2.35:判断下列定义推断出的类型是什么?然后编写程序进行验证。
const int i = 42;
auto j = i; // j是int型变量。auto一般会忽略顶层const
const auto &k = i; //k是int型常量的引用。
auto *p = &i; // p是指向int型常量的指针
const auto j2 = i, &k2 = i; //j2是int型常量,k2是整型常量的引用。
练习2.36:关于下面的代码,请指出每一个变量的类型以及程序结束时它们各自的值。
注意:
decltype的表达式如果是加上了括号的变量,结果将是引用。
int a = 3, b = 4; // a,b都是int型变量
decltype ( a ) c = a; //c为int型变量
decltype ( ( b ) ) d = a; //d为int型变量的引用
++c;
++d;
程序结束时,
a = 4
b = 4
c = 4
d = 4
练习2.37:赋值是会产生引用的一类典型表达式,引用的类型就是左值的类型。也就是说,如果i是int,则表达式 i = x 的类型是int &。根据这一特点,请指出下面的代码中每一个变量的类型和值。
int a = 3, b = 4;
decltype ( a ) c = a; // c是int型变量
decltype( a = b ) d = a; //d是int型变量的引用
注意:decltype括号中的表达式 a = b 作为decltype的参数,编译器分析表达式并得到它的类型作为d的推断类型,但是不实际计算该表达式,所以a的值不发生改变。
所以,最终 a c d的值都为3,b 的值为4。
练习2.38:说明由decltype指定类型和由auto指定类型有何区别?请举出一个例子,decltype指定的类型和auto指定的类型一样;再举一个例子,decltype指定的类型和auto指定的类型不一样。
auto 和 decltype的区别主要有三个方面:
第一,auto类型说明符用编译器计算变量的初始值来推断其类型,而decltype虽然也让编译器分析表达式并得到它的类型,但是不实际计算表达式的值。
第二,编译器推断出来的auto类型有时候和初始值的类型并不完全一样,编译器会适当地改变结果类型使其更符合初始化规则。例如,auto一般会忽略掉顶层const,而把底层const留下来。与之相反,decltype会保留变量的顶层const。
第三,与auto不同,decltype的结果类型与表达式形式密切相关,如果变量名加上了一对括号,则得到的类型与不加括号时会有不同。如果decltype使用的是一个不加括号的变量,则得到的结果就是该变量的类型;如果给变量加上了一层或多层括号,则编译器将推断得到引用类型。
练习2.39:编译下面的程序观察其运行结果,注意,如果忘记写类定义体后面的分号会发生什么情况?记录下相关信息,以后可能会有用。
error: expected ';' after struct definition|
练习2.40:根据自己的理解写出Sales_data类,最好与书中的例子有所区别。
//新增 零售价,实售价,折扣
struct Sales_data{
std::string bookNo;
unsigned units_sold = 0;
double selling_price = 0.0;
double sale_price = 0.0;
double discount = 0.0;
};
练习2.41:使用你的Sales_data类重写1.5.1节、1.5.2节和1.6节的练习。眼下先把Sales_data类的定义和main函数放在同一个文件里。
#include
#include
class Sales_data{
//友元函数
friend std::istream& operator >> ( std::istream&, Sales_data& );
friend std::ostream& operator << ( std::ostream&, const Sales_data& );
friend bool operator < ( const Sales_data&, const Sales_data& );
friend bool operator == ( const Sales_data&, const Sales_data& );
public: //构造函数
Sales_data() = default;
Sales_data( const std::string &book ): bookNo( book ) { }
Sales_data( std::istream &is ) { is >> *this; }
public:
Sales_data& operator += ( const Sales_data& );
std::string isbn() const { return bookNo; }
private:
std::string bookNo;
unsigned units_sold = 0;
double selling_price = 0.0;
double sale_price = 0.0;
double discount = 0.0;
};
inline bool compareIsbn( const Sales_data&lhs, const Sales_data&rhs )
{
return lhs.isbn() == rhs.isbn();
}
Sales_data operator + ( const Sales_data&, const Sales_data& );//声明
//友元函数定义
inline bool operator == ( const Sales_data &lhs, const Sales_data &rhs )
{
return lhs.units_sold == rhs.units_sold && lhs.sale_price == rhs.sale_price &&
lhs.isbn() == rhs.isbn();
}
inline bool operator != ( const Sales_data &lhs, const Sales_data &rhs )
{
return !( lhs == rhs );
}
//成员函数
Sales_data& Sales_data::operator += ( const Sales_data &rhs )
{
sale_price = ( rhs.sale_price * rhs.units_sold + sale_price * units_sold )
/( rhs.units_sold + units_sold );
units_sold += rhs.units_sold;
if( sale_price != 0 )
discount = sale_price / selling_price;
return *this;
}
Sales_data operator + ( const Sales_data &lhs, const Sales_data &rhs )
{
Sales_data tmp( lhs );
tmp += rhs;
return tmp;
}
std::istream& operator >> ( std::istream &in, Sales_data &s )
{
in >> s.bookNo >> s.units_sold >> s.selling_price >> s.sale_price;
if( in && s.selling_price != 0 )
s.discount = s.sale_price / s.selling_price;
else
s = Sales_data();
return in;
}
std::ostream& operator << ( std::ostream &out, const Sales_data &s )
{
out << s.isbn() << " " << s.units_sold << " "
<< s.selling_price << " " << s.sale_price << " " << s.discount;
return out;
}
int main()
{
/* 练习1.20 读取一组销售记录,将每条记录打印到标准输出 */
Sales_data book;
std::cout << "请输入销售记录(序号,销量,零售价,实售价):" << std::endl;
while( std::cin >> book )
std::cout << "ISBN、售出本数、原始价格、实售价格、折扣为" << book << std::endl;
std::cin.clear();//拟用错误输入退出循环,clear()用以消除流的错误状态
std::cin.ignore();//略过错误输入
/* 练习1.21 读取两个ISBN相同的Sales_data对象,输出它们的和 */
Sales_data trans1,trans2;
std::cout << "请输入两条ISBN相同的销售记录:" << std::endl;
std::cin >> trans1 >> trans2;
if( compareIsbn( trans1, trans2 ) )
std::cout << "汇总信息:ISBN、售出本数、原始价格、实售价格、折扣为"
<< trans1 + trans2 << std::endl;
else
std::cout << "两条销售记录的ISBN不同!" << std::endl;
std::cin.clear();
std::cin.ignore();
/*练习1.22 读取多个具有相同ISBN的销售记录,输出所有记录的和 */
Sales_data total,trans;
std::cout << "请输入几条相同ISBN的销售记录:" << std::endl;
if( std::cin >> total ) //读入第一条记录
{
while( std::cin >> trans )
{
if( compareIsbn( total, trans ) )
total += trans;
else
{
std::cout << "当前的ISBN不同" << std::endl;
break;
}
}
std::cout << "汇总信息:ISBN、售出本数、原始价格、实售价格、折扣为"
<< total << std::endl;
}
else
{
std::cout << "没有数据" << std::endl;
return -1;
}
int num = 1;
std::cout << "请输入若干销售记录:" << std::endl;
if( std::cin >> trans1 )
{
while( std::cin >> trans2 )
{
if( compareIsbn( trans1, trans2 ) )
num++;
else
{
std::cout << trans1.isbn() << "共有"
<< num << "条销售记录" << std::endl;
trans1 = trans2;
num = 1;
}
}
std::cout << trans1.isbn() << "共有"
<< num << "条销售记录" << std::endl;
}
else
{
std::cout << "没有数据" << std::endl;
return -1;
}
return 0;
}
练习2.42:
略了。只是上题的类声明放在头文件里。