c++ Primer 第七章:类 练习答案记录
练习题导航
- c++ Primer 第七章:类 练习答案记录
- 课前笔记
- 7.1 定义抽象数据类型
- 7.2 访问控制与封装
- 7.3 类的其他特性
- 7.4 类的作用域
- 7.5 构造函数再探
- 7.5.1 构造函数初始值列表
- 7.5.2 委托构造函数
- 7.5.3 默认构造函数的作用
- 7.5.4 隐式的类类型转换
- 练习7.47 说明接受一个string参数的Sales_data构造函数是否应该是explicit的,并解释这样做的优缺点
- 练习7.48 假定Sales_data的构造函数不是explicit的,则下述定义将执行什么样的操作?
- 练习7.49 对于combine 函数的三种不同声明,当我们调用i.combine(s) 时分别发生什么情况?其中 i 是一个 Sales_data,而 s 是一个string对象。
- 练习7.50 确定在你的Person 类中是否有一些构造函数应该是 explicit 的。
- 练习7.51 vector 将其单参数的构造函数定义成 explicit 的,而string则不是,你觉得原因何在?
- 7.5.5 聚合类
- 7.5.6 字面值常量类
- 7.6 类的静态成员
下面的练习别忘记都加上下面这一语句
#include<iostream>
课前笔记
为了使自己的程序有很好的移植性,c++程序员应该尽量使用size_t和size_type而不是int, unsigned
size_t是全局定义的类型;size_type是STL类中定义的类型属性,用以保存任意string和vector类对象的长度
string::size_type 制类型一般就是unsigned int, 但是不同机器环境长度可能不同 win32 和win64上长度差别;size_type一般也是unsigned int
使用的时候可以参考: string::size_type a =123; vector
<int>
size_type b=234; size_t b=456;size_t 使用的时候头文件需要
<cstddef>
;size_type 使用的时候需要<string>
或者<vector>
sizeof(string::size_type)
sizeof(vector<bool>
::size_type)
sizeof(vector<char>
::size_type)
sizeof(size_t)
上述长度均相等,长度为win32:4 win64:8二者联系:在用下标访问元素时,vector使用vector::size_type作为下标类型,而数组下标的正确类型则是size_t
7.1 定义抽象数据类型
7.1.1 设计Sales_data类
练习7.1 使用2.6.1节练习定义的Sales_data类为1.6节(第21页)的交易处理程序编写一个新版本
第二章练习2.41
7.1.2 定义改进的Sales_data类
练习7.2 曾在2.6.2节练习(第67页)中编写了一个Sales_data类,请向这个类添加combine和isbn成员
在源文件处新建一个头文件
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include<string>
using namespace std;
struct Sales_data
{
string bookNo; //bookNo被初始化为空字符串,书籍名字
unsigned units_sold = 0; //初始化0,销售额
double revenue = 0.0; //初始化0,平均价格
//一个isbn成员函数,用于返回对象的ISBN编号
string isbn() const { return bookNo; }
//一个combine成员函数,用于将一个Sales_data对象加到另一个对象上
Sales_data& combine(const Sales_data&);
};
Sales_data& Sales_data::combine(const Sales_data& rhs)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
#endif
练习7.3 修改7.1.1节(第229页)的交易处理程序,令其使用这些成员
#include"Sales_data.h"
using namespace std;
int main()
{
Sales_data total;
if (cin >> total.bookNo >> total.units_sold >> total.revenue) {
Sales_data trans;
while (cin >> trans.bookNo >> trans.units_sold >> trans.revenue) {
if (total.isbn() == trans.isbn()) {
total.combine(trans);
}
else {
cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl;
total = trans;
}
}
cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl;
}
else {
cerr << "NO data?!" << endl;
}
}
练习7.4 编写一个名为Person的类,使其表示人员的姓名和住址。使用string对象存放这些元素,接下来的练习将不断充实这个类的其他特征。
#ifndef PERSON_H
#define PERSON_H
#include<string>
using namespace std;
struct Person
{
string name; //人员姓名
string address; //住址
};
#endif
练习7.5 在你的Person类中提供一些操作使其能够返回姓名和住址。这些函数是否应该是const的呢?解释原因
应该是const,这两个成员函数只需读取成员对象,无需改变成员对象。
#ifndef PERSON_H
#define PERSON_H
#include<string>
using namespace std;
struct Person
{
string name; //人员姓名
string address; //住址
string get_name() const { return name; }
string get_address() const { return address; }
};
#endif
7.1.3 定义类相关的非成员函数
练习7.6 对于函数add、read和print,定义你自己的版本
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include<string>
using namespace std;
struct Sales_data
{
string bookNo; //bookNo被初始化为空字符串,书籍名字
unsigned units_sold = 0; //初始化0,销售额
double revenue = 0.0; //初始化0,平均价格
//一个isbn成员函数,用于返回对象的ISBN编号
string isbn() const { return bookNo; }
//一个combine成员函数,用于将一个Sales_data对象加到另一个对象上
Sales_data& combine(const Sales_data&);
};
Sales_data& Sales_data::combine(const Sales_data& rhs)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
istream& read(istream& is, Sales_data& item) //读取一本书
{
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
ostream& print(ostream& os, const Sales_data& item) //输出一本书
{
os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
return os;
}
Sales_data add(const Sales_data& lhs, const Sales_data& rhs)
{
Sales_data sum = lhs;
sum.combine(rhs);
return sum;
}
#endif
练习7.7 使用这些新函数重写7.1.2节(第233页)练习中的交易处理程序
#include<string>
#include"Sales_data.h"
using namespace std;
int main()
{
Sales_data total;
if (read(cin, total)) {
Sales_data trans;
while (read(cin, trans)) {
if (total.isbn() == trans.isbn())
total = add(total, trans);
else {
print(cout, total) << endl;
total = trans;
}
}
print(cout, total) << endl;
}
else {
cerr << "No data?!" << endl;
}
}
练习7.8 为什么read函数将其Sales_data参数定义成普通的引用,而print将其参数定义成常量引用?
因为read函数需要改变成员对象;而print只需读取成员对象
练习7.9 对于7.1.2节(第223页)练习中的代码,添加读取和打印Person对象的操作
#ifndef PERSON_H
#define PERSON_H
#include<string>
using namespace std;
struct Person
{
string name; //人员姓名
string address; //住址
string get_name() const { return name; }
string get_address() const { return address; }
};
istream& read(istream& is, Person& item)
{
is >> item.name >> item.address;
return is;
}
ostream& print(ostream& os, Person const& item)
{
os << item.name << " " << item.address << " ";
return os;
}
#endif
练习7.10 在下面这条if语句中,条件部分的作用是什么?
读入data1和data2,并判断返回是否为真
if(read(read(cin,data1),data2))
7.1.4 构造函数
练习7.11 在你的Sales_data类中添加构造函数,然后编写一段程序令其用到每个构造函数。
头文件
#pragma once
#ifndef SALES_DATA_H
#define SALES_DATA_H
//练习7.6 对于函数add、read和print,定义你自己的版本
#include<iostream>
#include<string>
using namespace std;
struct Sales_data
{
//内联自定义默认构造函数
Sales_data() = default;
Sales_data(const string& s) :bookNo(s) {}
Sales_data(const string &s, unsigned n, double p) :bookNo(s), units_sold(n), revenue(p* n) {}
Sales_data(istream&);
string bookNo; //bookNo被初始化为空字符串,书籍名字
unsigned units_sold = 0; //初始化0,销售额
double revenue = 0.0; //初始化0,销售总价
//一个isbn成员函数,用于返回对象的ISBN编号
string isbn() const { return bookNo; }
//一个combine成员函数,用于将一个Sales_data对象加到另一个对象上
Sales_data& combine(const Sales_data&);
//用于求平均价格
double avg_price() const; //平均价格
};
Sales_data& Sales_data::combine(const Sales_data& rhs) //把后加入的书销售额和销售总价加到上一本书中(同名)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
double Sales_data::avg_price() const //平均价格
{
if (units_sold) {
return revenue / units_sold;
}
else {
return 0;
}
}
istream& read(istream& is, Sales_data& item) //读取一本书
{
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
ostream& print(ostream& os, const Sales_data& item) //输出一本书
{
os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
return os;
}
Sales_data add(const Sales_data& lhs, const Sales_data& rhs)
{
Sales_data sum = lhs;
sum.combine(rhs);
return sum;
}
//内联自定义默认构造函数
Sales_data::Sales_data(istream& is)
{
read(is, *this);
}
#endif
主函数
#include<string>
#include"Sales_data.h"
using namespace std;
int main()
{
Sales_data sales_data1;
print(cout, sales_data1) << endl;
Sales_data sales_data2("1-01");
print(cout, sales_data2) << endl;
Sales_data sales_data3("1-01", 1, 100);
print(cout, sales_data3) << endl;
Sales_data sales_data4(cin);
print(cout, sales_data4) << endl;
// Sales_data sales_data5();
// print(cout, sales_data5) << endl;
return 0;
}
练习7.12 把只接受一个istream作为参数的构造函数定义移到类的内部
头文件
#pragma once
#ifndef SALES_DATA_H
#define SALES_DATA_H
//练习7.6 对于函数add、read和print,定义你自己的版本
#include<iostream>
#include<string>
using namespace std;
struct Sales_data;
istream& read(std::istream& is, Sales_data& item);
ostream& print(std::ostream& os, const Sales_data& item);
Sales_data add(const Sales_data& lhs, const Sales_data& rhs); //先声明
struct Sales_data
{
//内联自定义默认构造函数
Sales_data() = default;
Sales_data(const string& s) :bookNo(s) {}
Sales_data(const string &s, unsigned n, double p) :bookNo(s), units_sold(n), revenue(p* n) {}
Sales_data(istream& is) { read(is, *this); }
string bookNo; //bookNo被初始化为空字符串,书籍名字
unsigned units_sold = 0; //初始化0,销售额
double revenue = 0.0; //初始化0,销售总价
//一个isbn成员函数,用于返回对象的ISBN编号
string isbn() const { return bookNo; }
//一个combine成员函数,用于将一个Sales_data对象加到另一个对象上
Sales_data& combine(const Sales_data&);
//用于求平均价格
double avg_price() const; //平均价格
};
Sales_data& Sales_data::combine(const Sales_data& rhs) //把后加入的书销售额和销售总价加到上一本书中(同名)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
double Sales_data::avg_price() const //平均价格
{
if (units_sold) {
return revenue / units_sold;
}
else {
return 0;
}
}
istream& read(istream& is, Sales_data& item) //读取一本书
{
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
ostream& print(ostream& os, const Sales_data& item) //输出一本书
{
os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
return os;
}
Sales_data add(const Sales_data& lhs, const Sales_data& rhs)
{
Sales_data sum = lhs;
sum.combine(rhs);
return sum;
}
#endif
主函数
#include<string>
#include"Sales_data.h"
using namespace std;
int main()
{
Sales_data sales_data1;
print(cout, sales_data1) << endl;
Sales_data sales_data2("1-01");
print(cout, sales_data2) << endl;
Sales_data sales_data3("1-01", 1, 100);
print(cout, sales_data3) << endl;
Sales_data sales_data4(cin);
print(cout, sales_data4) << endl;
// Sales_data sales_data5();
// print(cout, sales_data5) << endl;
return 0;
}
练习7.13 使用istream构造函数重写第229页程序
#include<string>
#include"Sales_data.h"
using namespace std;
int main()
{
Sales_data total;
if (read(cin, total)) {
Sales_data trans;
while (read(cin, trans)) {
if (total.isbn() == trans.isbn())
total = add(total, trans);
else {
print(cout, total) << endl;
total = trans;
}
}
print(cout, total) << endl;
}
else {
cerr << "No data?!" << endl;
}
}
练习7.14 编写一个构造函数,令其用我们提供的类内初始值显式地初始化成员
Sales_data() : bookNo(""), units_sold(0), revenue(0) { }
练习7.15 为你的Person类添加正确的构造函数
#ifndef PERSON_H
#define PERSON_H
#include<iostream>
#include<string>
using namespace std;
istream& read(istream& is, Person& item);
ostream& print(ostream& os, Person const& item);
struct Person
{
//内联自定义默认构造函数
Person(const string& s) :name(s) {} //可以单独输入一个name
Person(const string& s1, const string& s2) :name(s1), address(s2) {} //可以输入name和address
Person(istream& is) { read(is, *this); }
string name; //人员姓名
string address; //住址
string get_name() const { return name; }
string get_address() const { return address; }
};
istream& read(istream& is, Person& item)
{
is >> item.name >> item.address;
return is;
}
ostream& print(ostream& os, Person const& item)
{
os << item.name << " " << item.address << " ";
return os;
}
#endif
7.1.5 拷贝、赋值和析构
7.2 访问控制与封装
练习7.16 在类的定义中对于访问说明符出现的位置和次数有限定吗?如果有,是什么?什么样的成员应该定义在public说明符之后?什么样的成员应该定义在private说明符之后?
1、一个类可以包含0个或多个访问说明符,而且对于某个访问说明符能出现多少次也没严格限定。
2、每个访问说明符指定了接下来的成员的访问级别,其有效范围直到出现下一个访问说明符或者达到类的结尾处为止。
3、定义在public说明符之后的成员在整个程序内可被访问,public成员定义类的接口
4、定义在private说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问,private部分封装了(即隐藏了)类的实现细节
练习7.17 使用class和struct时有区别吗?如果有,是什么?
使用class和struct定义类的唯一区别就是默认的访问权限。
练习7.18 封装是何含义?它有什么用处?
参考:https://blog.csdn.net/shamozhizhoutx/article/details/82291127
封装是实现与接口的分离。它隐藏了类型的实现细节。(在C++中,封装是通过将实现放在一个类的私有部分来实现的) 封装有两个重要的优点:
1.确保用户代码不会无意间破坏封装对象的状态;
2.被封装的类的具体实现细节可以随时改变,而无须调整用户级别的代码。
练习7.19 在你的Person类中,你将把哪些成员声明成public的?哪些声明成private的?解释你这样做的原因。
接口应该被定义为公共的,数据不应该暴露在类之外。
class Person
{
public:
//内联自定义默认构造函数
Person(const string& s) :name(s) {} //可以单独输入一个name
Person(const string& s1, const string& s2) :name(s1), address(s2) {} //可以输入name和address
Person(istream& is) { read(is, *this); }
string get_name() const { return name; }
string get_address() const { return address; }
private:
string name; //人员姓名
string address; //住址
};
7.2.1 友元
练习7.20 友元在什么时候有用?请分别列举出使用友元的利弊。
类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数成为它的友元。
优点: 外部函数可以方便地使用类的成员,而不需要显示地给它们加上类名; 可以方便地访问所有非公有成员; 有时,对类的用户更容易读懂。
缺点:减少封装和可维护性; 代码冗长,类内的声明,类外函数声明。
练习7.21 修改你的Sales_data类使其隐藏实现的细节。你之前编写的关于Sales_data操作的程序应该继续使用,借助类的新定义重新编译该程序,确保其工作正常。
头文件
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include<iostream>
#include<string>
using namespace std;
struct Sales_data;
istream& read(std::istream& is, Sales_data& item);
ostream& print(std::ostream& os, const Sales_data& item);
Sales_data add(const Sales_data& lhs, const Sales_data& rhs); //先声明
class Sales_data
{
friend istream& read(std::istream& is, Sales_data& item);
friend ostream& print(std::ostream& os, const Sales_data& item);
friend Sales_data add(const Sales_data& lhs, const Sales_data& rhs);
public:
//内联自定义默认构造函数
Sales_data() = default;
Sales_data(const string& s) :bookNo(s) {}
Sales_data(const string &s, unsigned n, double p) :bookNo(s), units_sold(n), revenue(p* n) {}
Sales_data(istream& is) { read(is, *this); }
//一个isbn成员函数,用于返回对象的ISBN编号
string isbn() const { return bookNo; }
//一个combine成员函数,用于将一个Sales_data对象加到另一个对象上
Sales_data& combine(const Sales_data&);
//用于求平均价格
double avg_price() const; //平均价格
private:
string bookNo; //bookNo被初始化为空字符串,书籍名字
unsigned units_sold = 0; //初始化0,销售额
double revenue = 0.0; //初始化0,销售总价
};
Sales_data& Sales_data::combine(const Sales_data& rhs) //把后加入的书销售额和销售总价加到上一本书中(同名)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
double Sales_data::avg_price() const //平均价格
{
if (units_sold) {
return revenue / units_sold;
}
else {
return 0;
}
}
istream& read(istream& is, Sales_data& item) //读取一本书
{
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
ostream& print(ostream& os, const Sales_data& item) //输出一本书
{
os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
return os;
}
Sales_data add(const Sales_data& lhs, const Sales_data& rhs)
{
Sales_data sum = lhs;
sum.combine(rhs);
return sum;
}
#endif
主函数
#include<string>
#include"Sales_data.h"
using namespace std;
int main()
{
Sales_data total;
if (read(cin, total)) {
Sales_data trans;
while (read(cin, trans)) {
if (total.isbn() == trans.isbn())
total = add(total, trans);
else {
print(cout, total) << endl;
total = trans;
}
}
print(cout, total) << endl;
}
else {
cerr << "No data?!" << endl;
}
}
练习7.22 修改你的Person类使其隐藏实现的细节
#ifndef PERSON_H
#define PERSON_H
#include<iostream>
#include<string>
using namespace std;
istream& read(istream& is, Person& item);
ostream& print(ostream& os, Person const& item);
class Person
{
friend istream& read(istream& is, Person& item);
friend ostream& print(ostream& os, Person const& item);
public:
//内联自定义默认构造函数
Person(const string& s) :name(s) {} //可以单独输入一个name
Person(const string& s1, const string& s2) :name(s1), address(s2) {} //可以输入name和address
Person(istream& is) { read(is, *this); }
string get_name() const { return name; }
string get_address() const { return address; }
private:
string name; //人员姓名
string address; //住址
};
istream& read(istream& is, Person& item)
{
is >> item.name >> item.address;
return is;
}
ostream& print(ostream& os, Person const& item)
{
os << item.name << " " << item.address << " ";
return os;
}
#endif
7.3 类的其他特性
7.3.1 类成员再探
练习7.23 编写你自己的Screen类
#ifndef SCREEN_H
#define SCREEB_H
#include<string>
#include<vector>
#include<iostream>
using namespace std;
class Screen
{
public:
typedef string::size_type pos; //用来定义类型的成员必须先定义后使用,这一点与普通成员有所区别,具体原因将在7.4.1节(第254页)解释。因此,类型成员通常出现在类开始的地方
private:
pos cursor = 0;
pos height = 0, width = 0;
string contents;
};
#endif // !SCREEN_H
练习7.24 给你的Screen类添加三个构造函数:一个默认构造函数;另一个构造函数接受宽和高的值,然后将contents初始化成给定数量的空白;第三个构造函数接受宽和高的值以及一个字符,该字符作为初始化之后屏幕的内容
#ifndef SCREEN_H
#define SCREEB_H
#include<string>
#include<vector>
#include<iostream>
using namespace std;
class Screen
{
public:
typedef string::size_type pos; //用来定义类型的成员必须先定义后使用,这一点与普通成员有所区别,具体原因将在7.4.1节(第254页)解释。因此,类型成员通常出现在类开始的地方
Screen() = default; //内联自定义默认构造函数
Screen(pos a, pos b) :height(a), width(b), contents(a* b, ' ') {} //构造函数接受宽和高的值,然后将contents初始化成给定数量的空白
Screen(pos a, pos b, const char& s) :height(a), width(b), contents(a* b, s) {} //构造函数接受宽和高的值以及一个字符
char get()const { return contents[cursor]; } //读取光标处的字符,隐式内联
char get(pos r, pos c)const { return contents[r * width + c]; } //移动光标,输入行和列,行乘以宽到达指定行后加上列达到光标所在字符并返回
private:
pos cursor = 0;
pos height = 0, width = 0;
string contents;
};
#endif // !SCREEN_H
练习7.25 Screen能安全地依赖于拷贝和赋值操作的默认版本吗?如果能,为什么?如果不能,为什么?
能,Screen类中只有内置类型和string可以使用拷贝和赋值操作,见7.15。
练习7.26 将Sales_data::avg_price定义成内联函数
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include<iostream>
#include<string>
using namespace std;
struct Sales_data;
istream& read(std::istream& is, Sales_data& item);
ostream& print(std::ostream& os, const Sales_data& item);
Sales_data add(const Sales_data& lhs, const Sales_data& rhs); //先声明
class Sales_data
{
friend istream& read(std::istream& is, Sales_data& item);
friend ostream& print(std::ostream& os, const Sales_data& item);
friend Sales_data add(const Sales_data& lhs, const Sales_data& rhs);
public:
//内联自定义默认构造函数
Sales_data() = default;
Sales_data(const string& s) :bookNo(s) {}
Sales_data(const string &s, unsigned n, double p) :bookNo(s), units_sold(n), revenue(p* n) {}
Sales_data(istream& is) { read(is, *this); }
//一个isbn成员函数,用于返回对象的ISBN编号
string isbn() const { return bookNo; }
//一个combine成员函数,用于将一个Sales_data对象加到另一个对象上
Sales_data& combine(const Sales_data&);
//用于求平均价格
private:
inline double avg_price() const; //平均价格
string bookNo; //bookNo被初始化为空字符串,书籍名字
unsigned units_sold = 0; //初始化0,销售额
double revenue = 0.0; //初始化0,销售总价
};
Sales_data& Sales_data::combine(const Sales_data& rhs) //把后加入的书销售额和销售总价加到上一本书中(同名)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
inline double Sales_data::avg_price() const //平均价格
{
if (units_sold) {
return revenue / units_sold;
}
else {
return 0;
}
}
istream& read(istream& is, Sales_data& item) //读取一本书
{
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
ostream& print(ostream& os, const Sales_data& item) //输出一本书
{
os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
return os;
}
Sales_data add(const Sales_data& lhs, const Sales_data& rhs)
{
Sales_data sum = lhs;
sum.combine(rhs);
return sum;
}
#endif
7.3.2 返回*this的成员函数
练习7.27 给你自己的Screen类添加move、set和display函数,通过执行下面的代码检验你的类是否正确
头文件
#ifndef SCREEN_H
#define SCREEB_H
#include<string>
#include<vector>
#include<iostream>
using namespace std;
class Screen
{
public:
typedef string::size_type pos; //用来定义类型的成员必须先定义后使用,这一点与普通成员有所区别,具体原因将在7.4.1节(第254页)解释。因此,类型成员通常出现在类开始的地方
Screen() = default; //内联自定义默认构造函数
Screen(pos a, pos b) :height(a), width(b), contents(a* b, ' ') {} //构造函数接受宽和高的值,然后将contents初始化成给定数量的空白
Screen(pos a, pos b, const char& s) :height(a), width(b), contents(a* b, s) {} //构造函数接受宽和高的值以及一个字符
char get()const { return contents[cursor]; } //读取光标处的字符,隐式内联
char get(pos r, pos c)const { return contents[r * width + c]; } //移动光标,输入行和列,行乘以宽到达指定行后加上列达到光标所在字符并返回
Screen& move(pos r, pos c);
Screen& set(char);
Screen& set(pos, pos, char);
Screen& display(ostream& os)
{
do_dispaly(os);
return *this;
}
const Screen& display(ostream& os)const
{
do_dispaly(os);
return *this;
}
private:
pos cursor = 0;
pos height = 0, width = 0;
string contents;
void do_dispaly(ostream& os)const { os << contents; }
};
inline Screen& Screen::move(pos r, pos c)
{
pos row = r * width; //计算行的位置
cursor = row + c; //在行内将光标移动到指定的列
return *this; //将this对象作为左值返回
}
inline Screen& Screen::set(char c)
{
contents[cursor] = c; //设置当前光标所在位置的新值
return *this; //将this对象作为左值返回
}
inline Screen& Screen::set(pos r, pos c, char ch)
{
contents[r * width + c] = ch; //设定给定位置的新值
return *this; //将this对象作为左值返回
}
#endif // !SCREEN_H
主函数
#include<string>
#include"Screen.h"
using namespace std;
int main()
{
Screen myScreen(5, 5, 'X');
myScreen.move(4, 0).set('#').display(cout);
cout << "\n";
myScreen.display(cout);
cout << "\n";
}
练习7.28 如果move、set和display函数的返回类型不是Screen&而是Screen,则在上一个练习中将会发生什么情况?
返回类型是Screen&的输出:
XXXXXXXXXXXXXXXXXXXX#XXXX
XXXXXXXXXXXXXXXXXXXX#XXXX
返回类型是Screen的输出:
XXXXXXXXXXXXXXXXXXXX#XXXX
XXXXXXXXXXXXXXXXXXXXXXXXX
因为这样的话move、set和display返回的是Screen的临时副本,后续set和display操作并不会改变myScreen。
练习7.29 修改你的Screen类,令move、set和display函数返回Screen并检查程序的运行结果,在上一个练习中你的推测正确吗?
头文件
#ifndef SCREEN_H
#define SCREEB_H
#include<string>
#include<vector>
#include<iostream>
using namespace std;
class Screen
{
public:
typedef string::size_type pos; //用来定义类型的成员必须先定义后使用,这一点与普通成员有所区别,具体原因将在7.4.1节(第254页)解释。因此,类型成员通常出现在类开始的地方
Screen() = default; //内联自定义默认构造函数
Screen(pos a, pos b) :height(a), width(b), contents(a* b, ' ') {} //构造函数接受宽和高的值,然后将contents初始化成给定数量的空白
Screen(pos a, pos b, const char& s) :height(a), width(b), contents(a* b, s) {} //构造函数接受宽和高的值以及一个字符
char get()const { return contents[cursor]; } //读取光标处的字符,隐式内联
char get(pos r, pos c)const { return contents[r * width + c]; } //移动光标,输入行和列,行乘以宽到达指定行后加上列达到光标所在字符并返回
Screen move(pos r, pos c);
Screen set(char);
Screen set(pos, pos, char);
Screen& display(ostream& os)
{
do_dispaly(os);
return *this;
}
const Screen& display(ostream& os)const
{
do_dispaly(os);
return *this;
}
private:
pos cursor = 0;
pos height = 0, width = 0;
string contents;
void do_dispaly(ostream& os)const { os << contents; }
};
inline Screen Screen::move(pos r, pos c)
{
pos row = r * width; //计算行的位置
cursor = row + c; //在行内将光标移动到指定的列
return *this; //将this对象作为左值返回
}
inline Screen Screen::set(char c)
{
contents[cursor] = c; //设置当前光标所在位置的新值
return *this; //将this对象作为左值返回
}
inline Screen Screen::set(pos r, pos c, char ch)
{
contents[r * width + c] = ch; //设定给定位置的新值
return *this; //将this对象作为左值返回
}
#endif // !SCREEN_H
主函数
#include<string>
#include"Screen.h"
using namespace std;
int main()
{
Screen myScreen(5, 5, 'X');
myScreen.move(4, 0).set('#').display(cout);
cout << "\n";
myScreen.display(cout);
cout << "\n";
}
练习7.30 通过this指针使用成员的做法虽然合法,但是有点多余。讨论显示地使用指针访问成员的优缺点.
优点: 更明确,减少误读的可能性; 可以使用名称与成员名相同的形参。
void setAddr(const std::string &addr) { this->addr = addr; }
缺点: 冗余代码增加。
std::string getAddr() const { return this->addr; } // unnecessary
7.3.3 类类型
练习7.31 定义一对类X和Y,其中X包含一个指向Y的指针,而Y包含一个类型为X的对象
#include<string>
using namespace std;
class X
{
Y* y = nullptr;
};
class Y
{
X A;
};
7.3.4 友元再探
练习7.32 定义你自己的Screen和Window_mgr,其中claear是Window_mgr的成员,是Screen的友元。
#ifndef SCREEN_H
#define SCREEB_H
#include<string>
#include<vector>
#include<iostream>
using namespace std;
class Window_mgr //Screen的构造函数接受两个尺寸参数和一个字符值,创建了一个给定大小的空白屏幕对象(24*80)
{
public:
using ScreenIndex = vector<Screen>::size_type;
void clear(ScreenIndex);
private:
vector<Screen> screens{ Screen(24,80,' ') }; //定义一个高24,宽80的空字符Screen
};
class Screen
{
friend void Window_mgr::clear(ScreenIndex i);
public:
typedef string::size_type pos; //用来定义类型的成员必须先定义后使用,这一点与普通成员有所区别,具体原因将在7.4.1节(第254页)解释。因此,类型成员通常出现在类开始的地方
Screen() = default; //内联自定义默认构造函数
Screen(pos a, pos b) :height(a), width(b), contents(a* b, ' ') {} //构造函数接受宽和高的值,然后将contents初始化成给定数量的空白
Screen(pos a, pos b, const char& s) :height(a), width(b), contents(a* b, s) {} //构造函数接受宽和高的值以及一个字符
char get()const { return contents[cursor]; } //读取光标处的字符,隐式内联
char get(pos r, pos c)const { return contents[r * width + c]; } //移动光标,输入行和列,行乘以宽到达指定行后加上列达到光标所在字符并返回
Screen& move(pos r, pos c);
Screen& set(char);
Screen& set(pos, pos, char);
Screen& display(ostream& os)
{
do_dispaly(os);
return *this;
}
const Screen& display(ostream& os)const
{
do_dispaly(os);
return *this;
}
private:
pos cursor = 0;
pos height = 0, width = 0;
string contents;
void do_dispaly(ostream& os)const { os << contents; }
};
inline Screen& Screen::move(pos r, pos c)
{
pos row = r * width; //计算行的位置
cursor = row + c; //在行内将光标移动到指定的列
return *this; //将this对象作为左值返回
}
inline Screen& Screen::set(char c)
{
contents[cursor] = c; //设置当前光标所在位置的新值
return *this; //将this对象作为左值返回
}
inline Screen& Screen::set(pos r, pos c, char ch)
{
contents[r * width + c] = ch; //设定给定位置的新值
return *this; //将this对象作为左值返回
}
void Window_mgr::clear(ScreenIndex i)
{
Screen& s = screens[i];
s.contents = string(s.height * s.width, ' ');
}
#endif // !SCREEN_H
7.4 类的作用域
练习7.33 如果我们给Screen添加一个如下所示的size成员将发生什么情况?如果出现了问题,请尝试修改它
#ifndef SCREEN_H
#define SCREEB_H
#include<string>
#include<vector>
#include<iostream>
using namespace std;
class Window_mgr //Screen的构造函数接受两个尺寸参数和一个字符值,创建了一个给定大小的空白屏幕对象(24*80)
{
public:
using ScreenIndex = vector<Screen>::size_type;
void clear(ScreenIndex);
private:
vector<Screen> screens{ Screen(24,80,' ') }; //定义一个高24,宽80的空字符Screen
};
class Screen
{
friend void Window_mgr::clear(ScreenIndex i);
public:
typedef string::size_type pos; //用来定义类型的成员必须先定义后使用,这一点与普通成员有所区别,具体原因将在7.4.1节(第254页)解释。因此,类型成员通常出现在类开始的地方
Screen() = default; //内联自定义默认构造函数
Screen(pos a, pos b) :height(a), width(b), contents(a* b, ' ') {} //构造函数接受宽和高的值,然后将contents初始化成给定数量的空白
Screen(pos a, pos b, const char& s) :height(a), width(b), contents(a* b, s) {} //构造函数接受宽和高的值以及一个字符
char get()const { return contents[cursor]; } //读取光标处的字符,隐式内联
char get(pos r, pos c)const { return contents[r * width + c]; } //移动光标,输入行和列,行乘以宽到达指定行后加上列达到光标所在字符并返回
Screen& move(pos r, pos c);
Screen& set(char);
Screen& set(pos, pos, char);
Screen& display(ostream& os)
{
do_dispaly(os);
return *this;
}
const Screen& display(ostream& os)const
{
do_dispaly(os);
return *this;
}
pos size()const;
private:
pos cursor = 0;
pos height = 0, width = 0;
string contents;
void do_dispaly(ostream& os)const { os << contents; }
};
inline Screen& Screen::move(pos r, pos c)
{
pos row = r * width; //计算行的位置
cursor = row + c; //在行内将光标移动到指定的列
return *this; //将this对象作为左值返回
}
inline Screen& Screen::set(char c)
{
contents[cursor] = c; //设置当前光标所在位置的新值
return *this; //将this对象作为左值返回
}
inline Screen& Screen::set(pos r, pos c, char ch)
{
contents[r * width + c] = ch; //设定给定位置的新值
return *this; //将this对象作为左值返回
}
void Window_mgr::clear(ScreenIndex i)
{
Screen& s = screens[i];
s.contents = string(s.height * s.width, ' ');
}
Screen::pos Screen::size() const
{
return height * width;
}
#endif // !SCREEN_H
7.4.1 名字查找与类的作用域
练习7.34 如果我们把第256页Screen类的pos的typedef放在类的最后一行会发生什么情况?
dummy_fcn(pos height)中的pos未声明,将会报错。
练习7.35 解释下面代码的含义,说明其中的Type和initVal分别使用了哪个定义。如果代码存在错误,尝试修改它。
修改前
#ifndef EXERCISE_H
#define EXERCISE_H
#include<string>
#include<iostream>
using namespace std;
typedef string Type;
Type initVal();
class Exercise
{
public:
typedef double Type;
Type setVal(Type);
Type initVal();
private:
int val;
};
Type Exercise::setVal(Type parm) {
val = parm + initVal();
return val;
}
#endif // !EXERCISE_H
修改后
#ifndef EXERCISE_H
#define EXERCISE_H
#include<string>
#include<iostream>
using namespace std;
typedef string Type;
Type initVal();
class Exercise
{
public:
typedef double Type;
Type setVal(Type);
Type initVal();
private:
int val;
};
Exercise::Type Exercise::setVal(Type parm) {
val = parm + initVal();
return val;
}
#endif // !EXERCISE_H
在type前加一个类名
Exercise::Type Exercise::setVal(Type parm) {
val = parm + initVal();
return val;
}
7.5 构造函数再探
7.5.1 构造函数初始值列表
练习7.36 下面的初始值是错误的,请找出问题所在并尝试修改它。
修改前
struct X {
X(int i, int j) :base(i), rem(base % j) {}
int rem, base;
};
修改后,成员的初始化顺序与它们在类定义中的出现顺序一致,所以会先初始化rem再初始化base,初始化rem时会用到base,故程序出错。可以改变定义的顺序
struct X {
X(int i, int j) :base(i), rem(base % j) {}
int base, rem;
};
练习7.37 使用本节提供的Sales_data类,确定初始化下面的变量时分别使用了哪个构造函数,然后罗列出每个对象所有数据成员的值
参考:https://blog.csdn.net/shamozhizhoutx/article/details/82291127
#include<string>
#include"Sales_data.h"
using namespace std;
Sales_data first_item(cin); // use Sales_data(std::istream &is) ; its value are up to your input.
int main() {
Sales_data next; // use Sales_data(std::string s = ""); bookNo = "", cnt = 0, revenue = 0.0
Sales_data last("9-999-99999-9"); // use Sales_data(std::string s = ""); bookNo = "9-999-99999-9", cnt = 0, revenue = 0.0
}
练习7.38 有些情况下我们希望提供cin作为接受istream& 参数的构造函数的默认实参,请声明这样的构造函数。
Sales_data(istream& is) { read(is, *this); }
练习7.39 如果接受string 的构造函数和接受 istream& 的构造函数都使用默认实参,这种行为合法吗?如果不,为什么?
非法。因为这样的话,重载构造函数Sale_data()将不明确。
练习7.40 从下面的抽象概念中选择一个(或者你自己指定一个),思考这样的类需要哪些数据成员,提供一组合理的构造函数并阐明这样做的原因。
(a) Book
(b) Data
(c) Employee
(d) Vehicle
(e) Object
(f) Tree
#ifndef OBJECT_H
#define OBJECT_H
#include<iostream>
#include<string>
using namespace std;
class Object
{
using pos = size_t;
public:
//内联自定义默认构造函数
Object() = default;
Object(const string& s, pos lt, pos wt, pos ht) :name(s), longth(lt), width(wt), height(ht), volume(lt* wt* ht) {}
Object(const string& s) :name(s) {};
Object(pos lt, pos wt, pos ht) :volume(lt* wt* ht) { }
private:
pos height, width, longth; //物体高、宽、长
string name; //物体名字
pos volume; //物体体积
};
#endif // !OBJECT_H
7.5.2 委托构造函数
练习7.41 使用委托构造函数重新编写你的Sales_data类,给每个构造函数体添加一条语句,令其一旦执行就打印一条信息。用各种可能的方式分别创建Sales_data对象,认真研究每次输出的信息,直到你确实理解了委托构造函数的执行顺序。
第一次修改
主函数
#include<string>
#include"Sales_data.h"
using namespace std;
int main()
{
Sales_data book1;
}
头文件
这里主要是将Sales_data()=default;
修改了成Sales_data() :Sales_data(" ", 0, 0) { cout << "go" << endl; }
在类外部声明Sales_data data;时默认bookNo为空,units_sold = 0,revenue = 0.0并返回一条语句go
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include<iostream>
#include<string>
using namespace std;
struct Sales_data;
istream& read(std::istream& is, Sales_data& item);
ostream& print(std::ostream& os, const Sales_data& item);
Sales_data add(const Sales_data& lhs, const Sales_data& rhs); //先声明
class Sales_data
{
friend istream& read(std::istream& is, Sales_data& item);
friend ostream& print(std::ostream& os, const Sales_data& item);
friend Sales_data add(const Sales_data& lhs, const Sales_data& rhs);
public:
//内联自定义默认构造函数,这里还加上了委托构造函数
Sales_data() :Sales_data(" ", 0, 0) { cout << "go" << endl; } //在类外部声明Sales_data data;时默认bookNo为空,units_sold = 0,revenue = 0.0并返回一条语句go
Sales_data(const string& s) :bookNo(s) {}
Sales_data(const string &s, unsigned n, double p) :bookNo(s), units_sold(n), revenue(p* n) {}
Sales_data(istream& is) { read(is, *this); }
//一个isbn成员函数,用于返回对象的ISBN编号
string isbn() const { return bookNo; }
//一个combine成员函数,用于将一个Sales_data对象加到另一个对象上
Sales_data& combine(const Sales_data&);
//用于求平均价格
private:
inline double avg_price() const; //平均价格
string bookNo; //bookNo被初始化为空字符串,书籍名字
unsigned units_sold = 0; //初始化0,销售额
double revenue = 0.0; //初始化0,销售总价
};
Sales_data& Sales_data::combine(const Sales_data& rhs) //把后加入的书销售额和销售总价加到上一本书中(同名)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
inline double Sales_data::avg_price() const //平均价格
{
if (units_sold) {
return revenue / units_sold;
}
else {
return 0;
}
}
istream& read(istream& is, Sales_data& item) //读取一本书
{
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
ostream& print(ostream& os, const Sales_data& item) //输出一本书
{
os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
return os;
}
Sales_data add(const Sales_data& lhs, const Sales_data& rhs)
{
Sales_data sum = lhs;
sum.combine(rhs);
return sum;
}
#endif
执行主函数不出所料返回了一个go
第二次修改
主函数
#include<string>
#include"Sales_data.h"
using namespace std;
int main()
{
string name = "1-0-1";
Sales_data book1;
Sales_data book2(name);
Sales_data book3("1-0-2");
}
头文件
这里主要是将Sales_data(const string& s) :bookNo(s) {}
修改了成Sales_data(const string& s) :Sales_data(s, 0, 0) { cout << "go" << endl; }
在类外部声明Sales_data data(s);
时默认bookNo为s,units_sold = 0,revenue = 0.0并返回一条语句go
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include<iostream>
#include<string>
using namespace std;
struct Sales_data;
istream& read(std::istream& is, Sales_data& item);
ostream& print(std::ostream& os, const Sales_data& item);
Sales_data add(const Sales_data& lhs, const Sales_data& rhs); //先声明
class Sales_data
{
friend istream& read(std::istream& is, Sales_data& item);
friend ostream& print(std::ostream& os, const Sales_data& item);
friend Sales_data add(const Sales_data& lhs, const Sales_data& rhs);
public:
//内联自定义默认构造函数,这里还加上了委托构造函数
Sales_data() :Sales_data(" ", 0, 0) { cout << "go" << endl; } //在类外部声明Sales_data data;时默认bookNo为空,units_sold = 0,revenue = 0.0并返回一条语句go
Sales_data(const string& s) :Sales_data(s, 0, 0) { cout << "go" << endl; } //在类外部声明Sales_data data(s);时默认bookNo为s,units_sold = 0,revenue = 0.0并返回一条语句go
//Sales_data(const string& s) :bookNo(s) {}
Sales_data(const string &s, unsigned n, double p) :bookNo(s), units_sold(n), revenue(p* n) {}
Sales_data(istream& is) { read(is, *this); }
//一个isbn成员函数,用于返回对象的ISBN编号
string isbn() const { return bookNo; }
//一个combine成员函数,用于将一个Sales_data对象加到另一个对象上
Sales_data& combine(const Sales_data&);
//用于求平均价格
private:
inline double avg_price() const; //平均价格
string bookNo; //bookNo被初始化为空字符串,书籍名字
unsigned units_sold = 0; //初始化0,销售额
double revenue = 0.0; //初始化0,销售总价
};
Sales_data& Sales_data::combine(const Sales_data& rhs) //把后加入的书销售额和销售总价加到上一本书中(同名)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
inline double Sales_data::avg_price() const //平均价格
{
if (units_sold) {
return revenue / units_sold;
}
else {
return 0;
}
}
istream& read(istream& is, Sales_data& item) //读取一本书
{
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
ostream& print(ostream& os, const Sales_data& item) //输出一本书
{
os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
return os;
}
Sales_data add(const Sales_data& lhs, const Sales_data& rhs)
{
Sales_data sum = lhs;
sum.combine(rhs);
return sum;
}
#endif
修改后
主函数
#include<string>
#include"Sales_data.h"
using namespace std;
int main()
{
string name = "1-0-1";
Sales_data book1;
Sales_data book2(name);
Sales_data book3("1-0-2");
Sales_data book4(cin);
}
头文件
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include<iostream>
#include<string>
using namespace std;
struct Sales_data;
istream& read(std::istream& is, Sales_data& item);
ostream& print(std::ostream& os, const Sales_data& item);
Sales_data add(const Sales_data& lhs, const Sales_data& rhs); //先声明
class Sales_data
{
friend istream& read(std::istream& is, Sales_data& item);
friend ostream& print(std::ostream& os, const Sales_data& item);
friend Sales_data add(const Sales_data& lhs, const Sales_data& rhs);
public:
//内联自定义默认构造函数,这里还加上了委托构造函数
Sales_data(const string& s, unsigned n, double p) :bookNo(s), units_sold(n), revenue(p* n) {} //非委托构造函数使用对应的实参初始化成员
Sales_data() :Sales_data(" ", 0, 0) { cout << "go" << endl; } //在类外部声明Sales_data data;时默认bookNo为空,units_sold = 0,revenue = 0.0并返回一条语句go
Sales_data(const string& s) :Sales_data(s, 0, 0) { cout << "go" << endl; } //在类外部声明Sales_data data(s);时默认bookNo为s,units_sold = 0,revenue = 0.0并返回一条语句go
Sales_data(istream& is) :Sales_data() { read(is, *this); cout << "go" << endl; } //委托给了默认构造参数变成了Sales_data(" ", 0, 0),之后再read
//Sales_data()=default;
//Sales_data(const string& s) :bookNo(s) {}
//Sales_data(istream& is) { read(is, *this); }
//一个isbn成员函数,用于返回对象的ISBN编号
string isbn() const { return bookNo; }
//一个combine成员函数,用于将一个Sales_data对象加到另一个对象上
Sales_data& combine(const Sales_data&);
//用于求平均价格
private:
inline double avg_price() const; //平均价格
string bookNo; //bookNo被初始化为空字符串,书籍名字
unsigned units_sold = 0; //初始化0,销售额
double revenue = 0.0; //初始化0,销售总价
};
Sales_data& Sales_data::combine(const Sales_data& rhs) //把后加入的书销售额和销售总价加到上一本书中(同名)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
inline double Sales_data::avg_price() const //平均价格
{
if (units_sold) {
return revenue / units_sold;
}
else {
return 0;
}
}
istream& read(istream& is, Sales_data& item) //读取一本书
{
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
ostream& print(ostream& os, const Sales_data& item) //输出一本书
{
os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
return os;
}
Sales_data add(const Sales_data& lhs, const Sales_data& rhs)
{
Sales_data sum = lhs;
sum.combine(rhs);
return sum;
}
#endif
练习7.42 对于你在练习7.40(参见7.5.1节,第261页)中编写的类,确定哪些构造函数可以使用委托。如果可以的话,编写委托构造函数。如果不可以,从抽象概念列表中重新选择一个你认为可以使用委托构造函数的,为挑选出的这个概念编写类定义。
#ifndef OBJECT_H
#define OBJECT_H
#include<iostream>
#include<string>
using namespace std;
class Object
{
using pos = size_t;
public:
//内联自定义默认构造函数
Object(const string& s, pos lt, pos wt, pos ht) :name(s), longth(lt), width(wt), height(ht), volume(lt* wt* ht) {}
//Object() = default;
//Object(const string& s) :name(s) {};
//Object(pos lt, pos wt, pos ht) :volume(lt* wt* ht) { }
//委托构造函数
Object() :Object(" ", 0, 0, 0) {}
Object(const string& s) :Object(s, 0, 0, 0) {}
Object(pos lt, pos wt, pos ht) :Object(" ", lt, wt, ht) {}
private:
pos height, width, longth; //物体高、宽、长
string name; //物体名字
pos volume; //物体体积
};
#endif // !OBJECT_H
7.5.3 默认构造函数的作用
练习7.43 假定有一个名为NoDefault的类,它有一个接受int的构造函数,但是没有默认构造函数。定义类C,C有一个Nodefault类型的成员,定义C的默认构造函数
#ifndef NODEFAULT_H
#define NODEFAULT_T
#include<string>
#include<iostream>
using namespace std;
class NoDefault
{
public:
NoDefault(int j) :i(j) {}
private:
int i;
};
class C
{
public:
C() :a(0) {}
private:
NoDefault a;
};
#endif // !NODEFAULT_H
练习7.44 下面这条声明合法吗?如果不,为什么?
非法,因为NoDefault没有默认构造函数
#include<vector>
#include"NoDefault.h"
int main()
{
vector<NoDefault> vec(10);
}
练习7.45 如果在上一个练习中定义的vector的元素类型是C,则声明合法吗?为什么?
合法,因为C有默认构造函数。
#include<vector>
#include"NoDefault.h"
int main()
{
vector<C> vec(10);
}
练习7.46 下面哪些论断是不正确的?为什么?
(a) 一个类必须至少提供一个构造函数。
不正确,没有构造函数时,有时可以生成默认构造函数;
(b)默认构造函数是参数列表为空的构造函数。
不正确,默认构造函数是没有构造函数的情况下,由编译器生成的构造函数;
©如果对于类来说不存在有意义的默认值,则类不应该提供默认构造函数。
不正确,默认构造函数在一些情况下非常重要;
(d)如果类没有定义默认构造函数,则编译器将为其生成一个并把每个数据成员初始化成相应类型的默认值。
不正确,当类没有显式地定义构造函数时,编译器才会隐式地定义默认构造函数。
7.5.4 隐式的类类型转换
练习7.47 说明接受一个string参数的Sales_data构造函数是否应该是explicit的,并解释这样做的优缺点
优点:防止隐式转换的产生;可以只用作初始化。
缺点:只有个单个参数的构造函数才有意义。
练习7.48 假定Sales_data的构造函数不是explicit的,则下述定义将执行什么样的操作?
都不会有问题。
#include<string>
#include"Sales_data.h"
using namespace std;
int main()
{
string null_isbn("9-999-99999-9");
Sales_data item1(null_isbn);
Sales_data item2("9-999-99999-9");
}
练习7.49 对于combine 函数的三种不同声明,当我们调用i.combine(s) 时分别发生什么情况?其中 i 是一个 Sales_data,而 s 是一个string对象。
(a)正确;
(b)不正确,combine的参数是非常量的引用,所以我们不能将临时参数传递给它,改成Sales_data&combine(const Sales_data&); 后正确;
(c)不正确,后面的const不对,this需要可改变的。
练习7.50 确定在你的Person 类中是否有一些构造函数应该是 explicit 的。
#ifndef PERSON_H
#define PERSON_H
#include<iostream>
#include<string>
using namespace std;
istream& read(istream& is, Person& item);
ostream& print(ostream& os, Person const& item);
class Person
{
friend istream& read(istream& is, Person& item);
friend ostream& print(ostream& os, Person const& item);
public:
//内联自定义默认构造函数
Person(const string& s) :name(s) {} //可以单独输入一个name
Person(const string& s1, const string& s2) :name(s1), address(s2) {} //可以输入name和address
explicit Person(istream& is) { read(is, *this); }
string get_name() const { return name; }
string get_address() const { return address; }
private:
string name; //人员姓名
string address; //住址
};
istream& read(istream& is, Person& item)
{
is >> item.name >> item.address;
return is;
}
ostream& print(ostream& os, Person const& item)
{
os << item.name << " " << item.address << " ";
return os;
}
#endif
练习7.51 vector 将其单参数的构造函数定义成 explicit 的,而string则不是,你觉得原因何在?
以下函数:
int getSize(const std::vector<int>&);
如果vector的构造函数没有explicit,
getSize(34);
我们就会不明白上述函数的意思。
stirng则不同,下述函数我们就很清楚。
void setYourName(std::string); // declaration.
setYourName("pezy"); // just fine.
7.5.5 聚合类
练习7.52 使用2.6.1节(第64页)的Sales_data类,解释下面的初始化过程。如果存在问题,尝试修改它
该初始化使用花括号括起来的成员初始值列表来初始化聚合类的数据成员。所以我们需要定义聚合类:
#include<string>
#include<vector>
using namespace std;
struct Sales_data {
string bookNo;
unsigned units_sold;
double revenue;
};
int main()
{
Sales_data item = { "978-0590353403",25,15.99 };
}
7.5.6 字面值常量类
练习7.53 定义你自己的Debug
#ifndef DEBUG_H
#define DEBUG_H
#include<string>
#include<vector>
#include<iostream>
using namespace std;
class Debug;
class Debug
{
public:
constexpr Debug(bool h, bool i, bool o) :hw(h), io(i), other(o) {}
constexpr Debug(bool h = true) : hw(h).io(h), other(h) {};
constexpr bool any() { return hw || io || other; }
void set_io(bool i) { io = i; }
void set_hw(bool h) { hw = h; }
void set_other(bool o) { other = o; }
private:
bool hw; //硬件错误,而非io错误
bool io; //IO错误
bool other; //其他错误
};
#endif // !DEBUG_H
练习7.54 Debug中以set_开头的成员应该被声明成constexpr吗?如果不,为什么?
在C++11中,constexpr函数时隐式的const,将不能更改数据成员;C++14中没有这个特点。
练习7.55 在7.5.5节(第266页)的Data类是字面值常量类吗?请解释原因
不是,std::string不是字面值类型。
7.6 类的静态成员
练习7.56 什么是类的静态成员?它有何优点?静态成员与普通成员有何区别?
类的静态成员与类本身直接相关,而不是与类的各个对象保持关联。
每个对象不需要存储公共数据,如果数据被改变,则每个对象都可以使用新值。
静态数据成员可以是不完全类型;
可以使用静态成员作为默认实参。
练习7.57 编写你自己的Account类
#ifndef ACCOUNT_H
#define ACCOUNT_H
#include<iostream>
#include<vector>
#include<string>
using namespace std;
class Account;
class Account
{
public:
void calculate() { amount += amount * interestRate; }
static double rate() { return interestRate; }
static void rate(double);
private:
string owner;
double amount;
static double interestRate;
static double initRate();
};
void Account::rate(double newRate)
{
interestRate = newRate;
}
#endif // !ACCOUNT_H
练习7.58 下面的静态数据成员的声明和定义有错误吗?请解释原因。
修改前
#ifndef EXAMPLE_H
#define EXAMPLE_H
#include<iostream>
#include<vector>
#include<string>
using namespace std;
class Example;
class Example {
public:
static double rate = 6.5;
static const int vecSize = 20;
static vector<double> vec(vecSize);
};
#endif // !EXAMPLE_H
#include<string>
#include<vector>
using namespace std;
#include "example.h"
int main()
{
double Example::rate;
vector<double> Example::vec;
}
修改后
#ifndef EXAMPLE_H
#define EXAMPLE_H
#include<iostream>
#include<vector>
#include<string>
using namespace std;
class Example;
class Example {
public:
static double rate;
static const int vecSize = 20;
static vector<double> vec;
};
#endif // !EXAMPLE_H
#include<string>
#include<vector>
using namespace std;
#include "example.h"
double Example::rate = 6.5;
vector<double> Example::vec(vecSize);