第二章 变量和基本类型
2.4 const限定符
关键字const 对变量的类型一旦限定,其值就不能再改变。所以,const对象必须初始化,初始值可以是任意复杂的表达式。
const int i = get_ size() ; //正确:运行时初始化
const int j = 42; //正确:编译时初始化
const int k; //错误:k是一个未经初始化的常量
如果想在多个文件之间共享const
对象,必须在变量的定义之前添加extern
关键字。
练习2.26
下面哪些语句是合法的?如果不合法,请说明为什么?
const int buf; // 不合法, const 对象必须初始化
int cnt = 0; // 合法
const int sz = cnt; // 合法
++cnt; ++sz; // ++sz,不合法, const 对象不能被改变
2.4.1 const 的引用
可以把引用绑定到const对象上,就像绑定到其他对象上一样,我们称之为对常量的引用(reference to const)。与普通引用不同的是,对常量的引用不能被用作修改它所绑定的对象:
const int ci = 1024;
const int &r1 = ci; //正确;引用及其对应的对象都是常量
rl=42; //错误:r1是对常量的引用
int &r2 = ci; //错误:试图让一个非常量引用指向一个常量对象
练习2.27
下面的哪些初始化是合法的?请说明原因。
int i = -1, &r = 0; // 不合法, r 必须引用一个对象
int *const p2 = &i2; // 合法
const int i = -1, &r = 0; // 合法
const int *const p3 = &i2; // 合法
const int *p1 = &i2; // 合法
const int &const r2; // 不合法, r2 是引用,引用没有顶层 const
const int i2 = i, &r = i; // 合法
练习2.28
说明下面的这些定义是什么意思,挑出其中不合法的。
int i, *const cp; // 不合法, const 指针必须初始化
int *p1, *const p2; // 不合法, const 指针必须初始化
const int ic, &r = ic; // 不合法, const int 必须初始化
const int *const p3; // 不合法, const 指针必须初始化
const int *p; // 合法. 一个指针,指向 const int
练习2.29
假设已有上一个练习中定义的那些变量,则下面的哪些语句是合法的?请说明原因。
i = ic; // 合法, 常量赋值给普通变量
p1 = p3; // 不合法, p3 是const指针不能赋值给普通指针
p1 = ⁣ // 不合法, 普通指针不能指向常量
p3 = ⁣ // 合法, p3 是常量指针且指向常量
p2 = p1; // 合法, 可以将普通指针赋值给常量指针
ic = *p3; // 合法, 对 p3 取值后是一个 int 然后赋值给 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 是顶层const,p2 是底层const,p3 既是顶层const又是底层const,r2 是底层const。
练习2.31
假设已有上一个练习中所做的那些声明,则下面的哪些语句是合法的?请说明顶层const和底层const在每个例子中有何体现。
r1 = v2; // 合法, 顶层const在拷贝时不受影响
p1 = p2; // 不合法, p2 是底层const,如果要拷贝必须要求 p1 也是底层const
p2 = p1; // 合法, int* 可以转换成const int*
p1 = p3; // 不合法, p3 是一个底层const,p1 不是
p2 = p3; // 合法, p2 和 p3 都是底层const,拷贝时忽略掉顶层const
练习2.32
下面的代码是否合法?如果非法,请设法将其修改正确。
int null = 0, *p = null;
非法.
把int变量直接赋给指针是错误的操作,即使int变量的值恰好等于0也不行.
int null = 0, *p = 0;
2.5 处理类型
练习2.33
利用本节定义的变量,判断下列语句的运行结果。
a=42; // a 是 int
b=42; // b 是一个 int,(ci的顶层const在拷贝时被忽略掉了)
c=42; // c 也是一个int
d=42; // d 是一个 int *,所以语句非法
e=42; // e 是一个 const int *, 所以语句非法
g=42; // g 是一个 const int 的引用,引用都是底层const,所以不能被赋值
练习2.34
练习2.35
判断下列定义推断出的类型是什么,然后编写程序进行验证。
const int i = 42;
auto j = i; const auto &k = i; auto *p = &i;
const auto j2 = i, &k2 = i;
j 是 int,k 是 const int的引用,p 是const int *,j2 是const int,k2 是 const int 的引用。
至于验证,鼠标移到变量上就出来了…
练习2.36
关于下面的代码,请指出每一个变量的类型以及程序结束时它们各自的值。
int a = 3, b = 4;
decltype(a) c = a;
decltype((b)) d = a;
++c;
++d;
c 是 int 类型,值为 4。d 是 int & 类型,绑定到 a,a 的值为 4 。
练习2.37
赋值是会产生引用的一类典型表达式,引用的类型就是左值的类型。也就是说,如果 i 是 int,则表达式 i=x 的类型是 int&。根据这一特点,请指出下面的代码中每一个变量的类型和值。
int a = 3, b = 4;
decltype(a) c = a;
decltype(a = b) d = a;
c 是 int 类型,值为 3。d 是 int& 类型,绑定到 a。
练习2.38
说明由decltype 指定类型和由auto指定类型有何区别。请举一个例子,decltype指定的类型与auto指定的类型一样;再举一个例子,decltype指定的类型与auto指定的类型不一样。
decltype 处理顶层const和引用的方式与 auto不同,decltype会将顶层const和引用保留起来。
int i = 0, &r = i;
//相同
auto a = i;
decltype(i) b = i;
//不同 d 是一个 int&
auto c = r;
decltype(r) d = r;
2.6 自定义数据结构
练习2.39
编译下面的程序观察其运行结果,注意,如果忘记写类定义体后面的分号会发生什么情况?记录下相关的信息,以后可能会有用。
struct Foo { /* 此处为空 */ } // 注意:没有分号
int main()
{
return 0;
}
提示应输入分号。
练习2.40
根据自己的理解写出 Sales_data 类,最好与书中的例子有所区别。
struct Sale_data
{
std::string bookNo;
std::string bookName;
unsigned units_sold = 0;
double revenue = 0.0;
double price = 0.0;
//...
}
练习2.41
使用你自己的Sale_data类重写1.5.1节(第20页)、1.5.2节(第21页)和1.6节(第22页)的练习。眼下先把Sales_data类的定义和main函数放在一个文件里。
####1.5.1
#include <iostream>
#include <string>
struct Sale_data
{
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
int main()
{
Sale_data book;
double price;
std::cin >> book.bookNo >> book.units_sold >> price;
book.revenue = book.units_sold * price;
std::cout << book.bookNo << " " << book.units_sold << " " << book.revenue << " " << price;
return 0;
}
####1.5.2
#include <iostream>
#include <string>
struct Sale_data
{
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
int main()
{
Sale_data book1, book2;
double price1, price2;
std::cin >> book1.bookNo >> book1.units_sold >> price1;
std::cin >> book2.bookNo >> book2.units_sold >> price2;
book1.revenue = book1.units_sold * price1;
book2.revenue = book2.units_sold * price2;
if (book1.bookNo == book2.bookNo)
{
unsigned totalCnt = book1.units_sold + book2.units_sold;
double totalRevenue = book1.revenue + book2.revenue;
std::cout << book1.bookNo << " " << totalCnt << " " << totalRevenue << " ";
if (totalCnt != 0)
std::cout << totalRevenue / totalCnt << std::endl;
else
std::cout << "(no sales)" << std::endl;
return 0;
}
else
{
std::cerr << "Data must refer to same ISBN" << std::endl;
return -1; // indicate failure
}
}
####1.6
#include <iostream>
#include <string>
struct Sale_data
{
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
int main()
{
Sale_data total;
double totalPrice;
if (std::cin >> total.bookNo >> total.units_sold >> totalPrice)
{
total.revenue = total.units_sold * totalPrice;
Sale_data trans;
double transPrice;
while (std::cin >> trans.bookNo >> trans.units_sold >> transPrice)
{
trans.revenue = trans.units_sold * transPrice;
if (total.bookNo == trans.bookNo)
{
total.units_sold += trans.units_sold;
total.revenue += trans.revenue;
}
else
{
std::cout << total.bookNo << " " << total.units_sold << " " << total.revenue << " ";
if (total.units_sold != 0)
std::cout << total.revenue / total.units_sold << std::endl;
else
std::cout << "(no sales)" << std::endl;
total.bookNo = trans.bookNo;
total.units_sold = trans.units_sold;
total.revenue = trans.revenue;
}
}
std::cout << total.bookNo << " " << total.units_sold << " " << total.revenue << " ";
if (total.units_sold != 0)
std::cout << total.revenue / total.units_sold << std::endl;
else
std::cout << "(no sales)" << std::endl;
return 0;
}
else
{
std::cerr << "No data?!" << std::endl;
return -1; // indicate failure
}
}