C++Primer 第七章 类
7.1定义抽象数据类型
7.1.1 设计Sales_data类
7.1 使用2.6.1节定义的Sales_data类为1.6节的交易处理程序编写一个新版本
#include <iostream>
#include <string>
using std::cin;
using std::cout;
using std::endl;
using std::string;
struct Sales_data
{
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
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.bookNo == trans.bookNo)
total.revenue += trans.revenue; // 更新总销售额
else
{
cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl; // 打印前本书结果
total.bookNo = trans.bookNo , total.revenue = trans.revenue , total.units_sold = trans.units_sold;//total表示当前这本书
}
}
cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl;
}
return 0;
}
7.2 曾在2.6.2节的练习中编写了一个Sales_data类,请向这个类添加combine函数和isbn成员。
7.3 修改7.1.1节的交易处理程序,令其使用这些成员。
Sales_data.h
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <string>
using std::string;
struct Sales_data
{
string isbn() const { return bookNo; }
Sales_data &combine(const Sales_data &);
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
Sales_data &Sales_data::combine(const Sales_data &temp)
{
units_sold += temp.units_sold;
revenue += temp.revenue;
return *this;
}
#endif
#include <iostream>
#include <string>
#include"Sales_data.h"
using std::cin;
using std::cout;
using std::endl;
using std::string;
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.bookNo = trans.bookNo, total.revenue = trans.revenue, total.units_sold = trans.units_sold; // total表示当前这本书
}
}
cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl;
}
return 0;
}
7.4 编写一个名为Person的类,使其表示人员的姓名和地址。使用string对象存放这些元素,接下来的练习将不断充实这个类的其他特征。
7.5 在你的Person类中提供一些操作使其能够返回姓名和地址。这些函数是否应该是const的呢?解释原因。
#ifndef PERSON_H
#define PERSON_H
#include<string>
using std::string;
struct Person
{
string name;
string address;
string get_name () const{return name;}
string get_address() const{return address;}
};
#endif
应该是const函数,它只需要读成员函数,不需要写成员函数
7.1.3 定义类相关的非成员函数
7.6 对于函数add、read和print,定义你自己的版本。
7.7 使用这些新函数重写7.1.2节练习中的程序。
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <string>
#include<iostream>
using std::string;
using std::istream;
using std::ostream;
struct Sales_data
{
string isbn() const { return bookNo; }
Sales_data &combine(const Sales_data &);
double avg_price() const {return revenue/units_sold;}
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
Sales_data &Sales_data::combine(const Sales_data &temp)
{
units_sold += temp.units_sold;
revenue += temp.revenue;
return *this;
}
istream &read(istream &is , Sales_data &item)
{
double price = 0.0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = item.units_sold * price;
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 <iostream>
#include <string>
#include"Sales_data.h"
using std::cin;
using std::cout;
using std::endl;
using std::string;
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);
cout << endl; // 打印前本书结果
total = trans; // total表示当前这本书
}
}
print(cout , total);
cout << endl;
}
return 0;
}
7.8 为什么read函数将其Sales_data参数定义成普通的引用,而print函数将其参数定义成常量引用?
read函数需要对Sales_data类对象进行写的操作,而print不需要。
7.9 对于7.1.2节练习中代码,添加读取和打印Person对象的操作。
#ifndef PERSON_H
#define PERSON_H
#include<string>
#include<iostream>
using std::string;
using std::istream;
using std::ostream;
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 , const Person &item)
{
os << item.name << item.address;
return os;
}
#endif
7.10 在下面这条if语句中,条件部分的作用是什么?
if (read(read(cin, data1), data2))//读入data1、data2数据,并判断返回结果是否为真
7.1.4 构造函数
7.11 在你的Sales_data类中添加构造函数,然后编写一段程序令其用到每个构造函数。
7.12 把只接受一个istream 作为参数的构造函数移到类的内部。
7.13 使用istream构造函数重写第229页的程序。
Sales_data.h
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <string>
#include <iostream>
using std::istream;
using std::ostream;
using std::string;
struct Sales_data;
istream &read(istream &is, Sales_data &item);
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(n * p) {}
Sales_data(istream &is){ read(is, *this); };
string isbn() const { return bookNo; }
Sales_data &combine(const Sales_data &);
double avg_price() const { return revenue / units_sold; }
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
Sales_data &Sales_data::combine(const Sales_data &temp)
{
units_sold += temp.units_sold;
revenue += temp.revenue;
return *this;
}
istream &read(istream &is, Sales_data &item)
{
double price = 0.0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = item.units_sold * price;
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 <iostream>
#include <string>
#include "Sales_data.h"
using std::cin;
using std::cout;
using std::endl;
using std::string;
int main()
{
Sales_data total(cin);
Sales_data trans;
if (!total.bookNo.empty())
{
while (read(cin, trans))
{
// 读取并处理剩余的交易记录
if (total.isbn() == trans.isbn())
total = add(total, trans); // 更新总销售额
else
{
print(cout, total);
cout << endl; // 打印前本书结果
total = trans; // total表示当前这本书
}
}
print(cout, total);
cout << endl;
}
return 0;
}
7.14 编写一个构造函数,令其用我们提供的类内初始值显式地初始化成员。
Sales_data() : bookNo(" "), units_sold(0), revenue(0){};
7.15 为你的 Person 类添加正确的构造函数。
#ifndef PERSON_H
#define PERSON_H
#include <string>
#include <iostream>
using std::istream;
using std::ostream;
using std::string;
struct Person;
istream &read(istream &is, Person &item);
struct Person
{
Person() = default;
Person(string &s, string &a) : name(s), address(a){}
Person(istream &is) {read(is, *this);}
string get_name() const { return name; }
string get_address() const { return address; }
string name;
string address;
};
istream &read(istream &is, Person &item)
{
is >> item.name >> item.address;
return is;
}
ostream &print(ostream &os, const Person &item)
{
os << item.name << item.address;
return os;
}
#endif
7.2 访问控制与封装
7.16 在类的定义中对于访问说明符出现的位置和次数有限定吗?如果有,是什么?什么样的成员应该定义在public 说明符之后?什么样的成员应该定义在private 说明符之后?
一个类可以包含0个或多个访问说明符,而且对于某个访问说明符能出现多少次也没有严格的限定。
public:成员在整个程序内可被访问,public成员定义类的接口;
private:成员可以被类的成员函数访问,但是不能被使用该类的代码访问,private部分封装了(即隐藏了)类的实现细节。
7.17 使用class 和 struct 时有区别吗?如果有,是什么?
struct默认的访问权限是public;
class默认的访问权限是private。
7.18 封装是何含义?它有什么用处?
封装是实现与接口的分离。它隐藏了类型的实现细节。(在C++中,封装是通过将实现放在一个类的私有部分来实现的)
封装有两个重要的优点:
1.确保用户代码不会无意间破坏封装对象的状态;
2.被封装的类的具体实现细节可以随时改变,而无须调整用户级别的代码。
7.19 在你的Person 类中,你将把哪些成员声明成public 的?哪些声明成private 的?解释你这样做的原因。
接口应该被定义为公共的,数据不应该暴露在类之外。
7.2.1 友元
7.20 友元在什么时候有用?请分别举出使用友元的利弊。
类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数成为它的友元。
优点:
外部函数可以方便地使用类的成员,而不需要显示地给它们加上类名;
可以方便地访问所有非公有成员;
有时,对类的用户更容易读懂。
缺点:
减少封装和可维护性;
代码冗长,类内的声明,类外函数声明。
7.21 修改你的Sales_data 类使其隐藏实现的细节。你之前编写的关于Sales_data操作的程序应该继续使用,借助类的新定义重新编译该程序,确保其正常工作。
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <string>
#include <iostream>
using std::istream;
using std::ostream;
using std::string;
struct Sales_data;
istream &read(istream &is, Sales_data &item);
struct Sales_data
{
friend istream &read(istream &is, Sales_data &item);
friend ostream &print(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(n * p) {}
// Sales_data() : bookNo(" "), units_sold(0), revenue(0){};
Sales_data(istream &is){ read(is, *this); };
string isbn() const { return bookNo; }
Sales_data &combine(const Sales_data &);
double avg_price() const { return revenue / units_sold; }
private:
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
Sales_data &Sales_data::combine(const Sales_data &temp)
{
units_sold += temp.units_sold;
revenue += temp.revenue;
return *this;
}
istream &read(istream &is, Sales_data &item)
{
double price = 0.0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = item.units_sold * price;
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
7.22 修改你的Person 类使其隐藏实现的细节。
#ifndef PERSON_H
#define PERSON_H
#include <string>
#include <iostream>
using std::istream;
using std::ostream;
using std::string;
struct Person;
istream &read(istream &is, Person &item);
struct Person
{
friend istream &read(istream &is, Person &item);
friend ostream &print(ostream &os, const Person &item);
public:
Person() = default;
Person(string &s, string &a) : name(s), address(a){}
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, const Person &item)
{
os << item.name << item.address;
return os;
}
#endif
7.3 类的其他特性
7.3.1 类成员再探
7.23 编写你自己的Screen 类型
7.24 给你的Screen 类添加三个构造函数:一个默认构造函数;另一个构造函数接受宽和高的值,然后将contents 初始化成给定数量的空白;第三个构造函数接受宽和高的值以及一个字符,该字符作为初始化后屏幕的内容。
#ifndef SCREEN_H
#define SCREEN_H
#include <string>
using std::string;
class Screen
{
public:
using pos = string::size_type;
Screen() = default;
Screen(pos ht, pos wd) : height(ht), width(wd){}
Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c){}
char get() const {return contents[cursor];}
inline char get(pos ht, pos wd) const;
Screen &move(pos r, pos c);
private:
pos cursor = 0;
pos height = 0, width = 0;
string contents;
};
char Screen::get(pos r, pos c) const
{
pos row = r * width;
return contents[row + c];
}
Screen &Screen::move(pos r, pos c)
{
pos row = r *width;
cursor = row + c;
return *this;
}
#endif
7.25 Screen 能安全地依赖于拷贝和赋值操作的默认版本吗?如果能,为什么?如果不能?为什么?
能,Screen类中只有内置类型和string可以使用拷贝和赋值操作
7.26 将Sales_data::avg_price 定义成内联函数。
在类外定义,函数名前加关键字inline
7.3.2 返回*this的成员函数
7.27 给你自己的Screen 类添加move、set 和display 函数,通过执行下面的代码检验你的类是否正确。
Screen myScreen(5, 5, 'X');
myScreen.move(4, 0).set('#').display(cout);
cout << "\n";
myScreen.display(cout);
cout << "\n";
Screen.h
#ifndef SCREEN_H
#define SCREEN_H
#include <string>
#include<iostream>
using std::string;
using std::ostream;
class Screen
{
public:
using pos = string::size_type;
Screen() = default;
Screen(pos ht, pos wd) : height(ht), width(wd){}
Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c){}
char get() const {return contents[cursor];}
inline char get(pos, pos) const;
Screen &move(pos, pos);
Screen &set(char);
Screen &set(pos, pos, char);
Screen &display(ostream &os){do_display(os); return *this;}
const Screen &display(ostream &os) const {do_display(os); return *this;}
private:
pos cursor = 0;
pos height = 0, width = 0;
string contents;
void do_display(ostream &os) const {os << contents;}
};
char Screen::get(pos r, pos c) const
{
pos row = r * width;
return contents[row + c];
}
Screen &Screen::move(pos r, pos c)
{
pos row = r *width;
cursor = row + c;
return *this;
}
Screen &Screen::set(char c)
{
contents[cursor] = c;
return *this;
}
Screen &Screen::set(pos r, pos col, char ch)
{
contents[r * width + col] = ch;
return *this;
}
#endif
7.28 如果move、set和display函数的返回类型不是Screen& 而是Screen,则在上一个练习中奖会发生什么?
7.29 修改你的Screen 类,令move、set和display函数返回Screen并检查程序的运行结果,在上一个练习中你的推测正确吗?
这样的话move、set和display返回的是Screen的临时副本,后续set和display操作并不会改变myScreen。
//无&:
XXXXXXXXXXXXXXXXXXXX#XXXX
XXXXXXXXXXXXXXXXXXXXXXXXX
//有&
XXXXXXXXXXXXXXXXXXXX#XXXX
XXXXXXXXXXXXXXXXXXXX#XXXX
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 的对象。
struct Y;
struct X
{
Y *p = nullptr;
};
struct Y
{
X x;
};
7.3.4 友元再探
7.32 定义你自己的Screen 和 Window_mgr,其中clear是Window_mgr的成员,是Screen的友元。
#ifndef SCREEN_H
#define SCREEN_H
#include <string>
#include<iostream>
#include<vector>
using std::string;
using std::ostream;
using std::vector;
class Window_mgr
{
public:
using ScreenIndex = vector<Screen>::size_type;
void clear(ScreenIndex);
private:
vector<Screen> screens{Screen(24, 80, ' ')};
};
class Screen
{
public:
friend void Window_mgr::clear(ScreenIndex);
//friend class Window_mgr;
using pos = string::size_type;
Screen() = default;
Screen(pos ht, pos wd) : height(ht), width(wd){}
Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c){}
char get() const {return contents[cursor];}
inline char get(pos, pos) const;
Screen move(pos, pos);
Screen set(char);
Screen set(pos, pos, char);
Screen display(ostream &os){do_display(os); return *this;}
const Screen display(ostream &os) const {do_display(os); return *this;}
private:
pos cursor = 0;
pos height = 0, width = 0;
string contents;
void do_display(ostream &os) const {os << contents;}
};
char Screen::get(pos r, pos c) const
{
pos row = r * width;
return contents[row + c];
}
Screen Screen::move(pos r, pos c)
{
pos row = r *width;
cursor = row + c;
return *this;
}
Screen Screen::set(char c)
{
contents[cursor] = c;
return *this;
}
Screen Screen::set(pos r, pos col, char ch)
{
contents[r * width + col] = ch;
return *this;
}
void Window_mgr::clear(ScreenIndex i)
{
Screen &s = screens[i];
s.contents = string(s.width * s.height, ' ');
}
#endif
7.4 类的作用域
7.33 如果我们给Screen 添加一个如下所示的size成员将发生什么情况?如果出现了问题,请尝试修改它。
pos Screen::size() const//Screen::pos,pos也需要加上作用域
{
return height * width;
}
7.4.1 名字查找与类的作用域
7.34 如果我们把第256页Screen类的pos的typedef放在类的最后一行会发生什么情况?
这样做会导致编译出错,因为对 pos 的使用出现在它的声明之前,此时编译器并不知道 pos 到底是什么含义。
7.35 解释下面代码的含义,说明其中的Type和initVal分别使用了哪个定义。如果代码存在错误,尝试修改它。
typedef string Type;
Type initVal(); // use `string`
class Exercise {
public:
typedef double Type;
Type setVal(Type); // use `double`
Type initVal(); // use `double`
private:
int val;
};
Type Exercise::setVal(Type parm) { // first is `string`, second is `double`
val = parm + initVal(); // Exercise::initVal()
return val;
}
7.5 构造函数再探
7.5.1 构造函数初始值列表
7.36 下面的初始值是错误的,请找出问题所在并尝试修改它。
struct X {
X (int i, int j): base(i), rem(base % j) {}//构造函数,先初始化rem,后初始化base。
int rem, base;//可以修改为int base, rem
};
7.37 使用本节提供的Sales_data类,确定初始化下面的变量时分别使用了哪个构造函数,然后罗列出每个对象所有的数据成员的值。
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(std::istream &);
7.39 如果接受string 的构造函数和接受 istream& 的构造函数都使用默认实参,这种行为合法吗?如果不,为什么?
非法。因为这样的话,重载构造函数Sale_data()将不明确。
7.40 从下面的抽象概念中选择一个(或者你自己指定一个),思考这样的类需要哪些数据成员,提供一组合理的构造函数并阐明这样做的原因。
#include<string>
#include<iostream>
using std::string;
using std::istream;
class Tree
{
private:
string id;
double price;
int height;
public:
Tree(string s = "") : id(s){};//定义默认构造函数
Tree(string s, double p, int h) : id(s), price(p), height(h) {}
Tree(istream &is) {is >> id >> price >> height;}
};
7.5.2 委托构造函数
7.41 使用委托构造函数重新编写你的Sales_data 类,给每个构造函数体添加一条语句,令其一旦执行就打印一条信息。用各种可能的方式分别创建 Sales_data 对象,认真研究每次输出的信息直到你确实理解了委托构造函数的执行顺序。
7.42 对于你在练习7.40中编写的类,确定哪些构造函数可以使用委托。如果可以的话,编写委托构造函数。如果不可以,从抽象概念列表中重新选择一个你认为可以使用委托构造函数的,为挑选出的这个概念编写类定义。
#include<string>
#include<iostream>
using std::string;
using std::istream;
class Tree
{
private:
string id;
double price;
int height;
public:
// Tree(string s = "") : id(s){};//定义默认构造函数
Tree(string s, double p, int h) : id(s), price(p), height(h) {}
// Tree(istream &is) {is >> id >> price >> height;}
Tree() : Tree("", 0, 0) {}
Tree(string s) : Tree(s, 0, 0){}
Tree(istream &is) : Tree() {is >> id >> price >> height;}
};
7.42 对于你在练习7.40中编写的类,确定哪些构造函数可以使用委托。如果可以的话,编写委托构造函数。如果不可以,从抽象概念列表中重新选择一个你认为可以使用委托构造函数的,为挑选出的这个概念编写类定义。
#include<string>
#include<iostream>
using std::string;
using std::istream;
class Tree
{
private:
string id;
double price;
int height;
public:
// Tree(string s = "") : id(s){};//定义默认构造函数
Tree(string s, double p, int h) : id(s), price(p), height(h) {}
// Tree(istream &is) {is >> id >> price >> height;}
Tree() : Tree("", 0, 0) {}
Tree(string s) : Tree(s, 0, 0){}
Tree(istream &is) : Tree() {is >> id >> price >> height;}
};
7.5.3 默认构造函数的作用
7.43 假定有一个名为 NoDefault 的类,它有一个接受 int 的构造函数,但是没有默认构造函数。定义类 C,C 有一个 NoDefault 类型的成员,定义C 的默认构造函数。
7.44 下面这条声明合法吗?如果不,为什么?
7.45 如果在上一个练习中定义的vector的元素类型是C,则声明合法吗?为什么?
#include<vector>
using std::vector;
struct NoDefault
{
public:
NoDefault(int ){}
};
struct C
{
private:
NoDefault n;
public:
C() : n(0) {}
};
int main()
{
vector<C> vec(10);//合法,C有默认构造函数
vector<NoDefault> vec(10);//不合法,NoDefault没有默认构造函数
}
7.46 下面哪些论断是不正确的?为什么?
(a) 一个类必须至少提供一个构造函数。
(b) 默认构造函数是参数列表为空的构造函数。
(c) 如果对于类来说不存在有意义的默认值,则类不应该提供默认构造函数。
(d) 如果类没有定义默认构造函数,则编译器将为其生成一个并把每个数据成员初始化成相应类型的默认值。
(a)不正确,没有构造函数时,有时可以生成默认构造函数;
(b)不正确,如果某个构造函数包含若干形参,但是同时为这些形参都提供了默认实参,则该构造函数也具备默认构造函数的功能;
(c)不正确,默认构造函数在一些情况下非常重要;
(d)不正确,当类没有显式地定义构造函数时,编译器才会隐式地定义默认构造函数。
7.5.4 隐式类型转换
7.47 说明接受一个string 参数的Sales_data构造函数是否应该是explicit的,并解释这样做的优缺点。
优点:
防止隐式转换的产生;
可以只用作初始化。
缺点:
只有个单个参数的构造函数才有意义。
7.48 假定Sales_data 的构造函数不是explicit的,则下述定义将执行什么样的操作?
string null_isbn("9-999-9999-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 的。
7.51 vector 将其单参数的构造函数定义成 explicit 的,而string则不是,你觉得原因何在?
int getSize(const std::vector<int>&);
getSize(34);//这个函数意思不好理解
void setYourName(std::string); // declaration.
setYourName("pezy"); // just fine.
7.5.5 聚合类
7.52 使用2.6.1节的 Sales_data 类,解释下面的初始化过程。如果存在问题,尝试修改它。
Sales_data item = {"987-0590353403", 25, 15.99};
用花括号括起来的成员初始值列表来初始化聚合类的数据成员
7.5.6 字面值常量类
7.53 定义你自己的 Debug。
7.54 Debug中以 set_ 开头的成员应该被声明成 constexpr 吗?如果不,为什么?
class Debug
{
public:
constexpr Debug(bool b = true) : hw(b), io(b), other(b) {}
constexpr Debug(bool h, bool i, bool o) : hw(h), io(i), other(o) {}
constexpr bool any() {return hw || io || other;}
void set_io(bool b) {io = b;}
void set_hw(bool b) {hw = b;}
void set_other(bool b) {other = b;}//如果是常量表达式,函数体内必须有且只有一条return语句
private:
bool hw;
bool io;
bool other;
};
7.55 7.5.5节的 Data 类是字面值常量类吗?请解释原因
不是,字面值常量类必须至少含有一个constexpr构造函数
7.6 类的静态成员
7.56 什么是类的静态成员?它有何优点?静态成员与普通成员有何区别?
类的静态成员与类本身直接相关,而不是与类的各个对象保持关联。
每个对象不需要存储公共数据,如果数据被改变,则每个对象都可以使用新值。
静态数据成员可以是不完全类型;
可以使用静态成员作为默认实参。
7.57 编写你自己的 Account 类。
#include<string>
using std::string;
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();
static constexpr int period = 30;
double daily_tbl[period];
};
double Account::interestRate = 0.0;
void Account::rate(double newRate)
{
interestRate = newRate;
}
constexpr int Account::period;
int main()
{
return 0;
}
7.58 下面的静态数据成员的声明和定义有错误吗?请解释原因。
// example.h
class Example {
public:
static double rate;
static const int vecSize = 20;
static vector<double> vec;
};
// example.C
#include "example.h"
double Example::rate = 6.5;
vector<double> Example::vec(vecSize);
//vector<double> Example::vec(Example::vecSize);