C++ Primer(第五版)|练习题答案与解析(第七章:类)
本博客主要记录C++ Primer(第五版)中的练习题答案与解析。
参考:C++ Primer
C++ Primer
练习题7.1
利用2.6.1节所定义的Sales_data类为1.6节的交易处理程序编写一个新的版本。
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.units_sold += trans.units_sold;
total.revenue += trans.revenue;
}
else
{
cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl;
total = trans;
}
}
cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl;
}
else
{
std::cerr << "No data?!" << std::endl;
return -1;
}
return 0;
}
练习题7.2
为上题中的Sales_data类添加combine和isbn成员。
struct Sales_data {
std::string isbn() const { return bookNo; };
Sales_data& combine(const Sales_data&);
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
Sales_data& Sales_data::combine(const Sales_data& rhs)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
练习题7.3
修改7.1.1节中的交易程序,令其使用这些成员。
#include <iostream>
#include <vector>
#include <string>
#include <initializer_list>
using namespace std;
struct Sales_data {
std::string isbn() const { return bookNo; };
Sales_data& combine(const Sales_data&);
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
Sales_data& Sales_data::combine(const Sales_data& rhs)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
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
{
std::cerr << "No data?!" << std::endl;
return -1;
}
return 0;
}
测试
ISN10123 5 5
ISN10123 1 10
^Z
ISN10123 6 15
练习题7.4
编写一个名为Person的类,使其表示人员的姓名和住址。使用string对象存放这些元素,接下来的练习将不断充实这个类的其他特征。
#include <string>
class Person {
std::string name;
std::string address;
};
练习题7.5
在你的Person类中提供一些操作使其能够返回姓名和住址。这些函数是否应该是const呢?解释原因。
class Person
{
public:
std::string name;
std::string address;
std::string getName() const { return name; }
std::string getAddr() const { return address; }
};
要使用const,因为这些函数并不改变它调用的对象的内容。
练习题7.6
对于函数add、read和print,定义你自己的版本。
#include <string>
#include <iostream>
struct Sales_data {
std::string const& isbn() const { return bookNo; };
Sales_data& combine(const Sales_data&);
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
// member functions.
Sales_data& Sales_data::combine(const Sales_data& rhs)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
// nonmember functions
std::istream &read(std::istream &is, Sales_data &item)
{
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
std::ostream &print(std::ostream &os, const Sales_data &item)
{
os << item.isbn() << " " << item.units_sold << " " << item.revenue;
return os;
}
Sales_data add(const Sales_data &lhs, const Sales_data &rhs)
{
Sales_data sum = lhs;
sum.combine(rhs);
return sum;
}
练习题7.7
使用这些新函数重写7.1.2节中的交易处理程序。
int main()
{
Sales_data total;
if (read(std::cin, total))
{
Sales_data trans;
while (read(std::cin, trans)) {
if (total.isbn() == trans.isbn())
total.combine(trans);
else {
print(std::cout, total) << std::endl;
total = trans;
}
}
print(std::cout, total) << std::endl;
}
else
{
std::cerr << "No data?!" << std::endl;
return -1;
}
return 0;
}
测试
ISN101023 5 10
ISN101023 1 10
^Z
ISN101023 6 60
练习题7.8
为什么read函数将其Sales_data参数定义成普通的引用,而print函数将其参数定义成常量引用?
因为print函数不会改变对象的值,但是read函数则会改变对象内容。P234
练习题7.9
对于7.1.2节练习中的代码,添加读取和打印Person对象的操作。
std::istream &read(std::istream &is, Person &person)
{
return is >> person.name >> person.address;
}
std::ostream &print(std::ostream &os, const Person &person)
{
return os << person.name << " " << person.address;
}
练习题7.10
在下面这条if语句中,条件部分的作用是什么?
if (read (read(cin, data), data))
一次连续读入两个data。read(cin, data)返回的还是cin的引用,在该输入流上继续读入数据。
练习题7.11
在你的Sales_data类中添加构造函数,然后编写一段程序令其用到每个构造函数。
#include <iostream>
#include <vector>
#include <string>
#include <initializer_list>
using namespace std;
#include <string>
#include <iostream>
struct Sales_data {
Sales_data() = default;
Sales_data(const std::string &s):bookNo(s) { }
Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(n*p){ }
Sales_data(std::istream &is);
std::string isbn() const { return bookNo; };
Sales_data& combine(const Sales_data&);
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
// nonmember functions
std::istream &read(std::istream &is, Sales_data &item)
{
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
std::ostream &print(std::ostream &os, const Sales_data &item)
{
os << item.isbn() << " " << item.units_sold << " " << item.revenue;
return os;
}
Sales_data add(const Sales_data &lhs, const Sales_data &rhs)
{
Sales_data sum = lhs;
sum.combine(rhs);
return sum;
}
// member functions.
Sales_data::Sales_data(std::istream &is)
{
read(is, *this);
}
Sales_data& Sales_data::combine(const Sales_data& rhs)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
int main()
{
Sales_data item1;
print(std::cout, item1) << std::endl;
Sales_data item2("0-201-78345-X");
print(std::cout, item2) << std::endl;
Sales_data item3("0-201-78345-X", 3, 20.00);
print(std::cout, item3) << std::endl;
Sales_data item4(std::cin);
print(std::cout, item4) << std::endl;
return 0;
}
测试:
0 0
0-201-78345-X 0 0
0-201-78345-X 3 60
ISN01023 5 7
ISN01023 5 35
练习题7.12
把只接受一个istream作为参数的构造函数定义到类的内部。
struct Sales_data;
std::istream& read (std::istream& is, Sales_data& item);
struct Sales_data
{
std::string bookNo; // 对象的ISBN编号
unsigned units_sold = 0; // 售出的册数
double revenue = 0.0; // 总价格
Sales_data() = default;
Sales_data(const std::string &s) : bookNo(s){}
Sales_data(const std::string &s, unsigned n, double r) : bookNo(s), units_sold(n), revenue(r) {}
Sales_data(std::istream& is){ read(is, *this); };
std::string isbn() const { return bookNo; } // 返回ISBN编号
Sales_data& combine (const Sales_data& rhs); // 将一个Sales_data对象加到另一个上面
};
练习题7.13
把只接受一个istream作为参数的构造函数定义到类的内部。
int main()
{
Sales_data total(std::cin);
if (!total.isbn().empty())
{
std::istream &is = std::cin;
while (is) {
Sales_data trans(is);
if (!is) break;
if (total.isbn() == trans.isbn())
total.combine(trans);
else {
print(std::cout, total) << std::endl;
total = trans;
}
}
print(std::cout, total) << std::endl;
}
else
{
std::cerr << "No data?!" << std::endl;
return -1;
}
return 0;
}
使用上一题的头文件,测试:
ISN10123 5 6
ISN10123 2 10
^Z
ISN10123 7 50
练习题7.14
编写一个构造函数,令其用我们提供的类内初始值显示地初始化成员。
Sales_data () : bookNo(""), units_sold(0), revenue(0) { }
练习题7.15
为你的Person类添加正确的构造函数。
#include <string>
#include <iostream>
struct Person;
std::istream &read(std::istream&, Person&);
struct Person {
Person() = default;
Person(const std::string sname, const std::string saddr):name(sname), address(saddr){ }
Person(std::istream &is){ read(is, *this); }
std::string getName() const { return name; }
std::string getAddress() const { return address; }
std::string name;
std::string address;
};
std::istream &read(std::istream &is, Person &person)
{
is >> person.name >> person.address;
return is;
}
std::ostream &print(std::ostream &os, const Person &person)
{
os << person.name << " " << person.address;
return os;
}
练习题7.16
在类的定义中,对于访问说明符出现的位置和次数有限定吗?如果有,是什么?什么样的成员应该定义在public后?什么样的应该定义在private后?
一个类对访问说明符出现的次数和位置并没有严格的限定。构造函数和接口函数定义在public之后,而数据成员和部分成员函数定义在private后面。P241
练习题7.17
使用class和struct时有什么区别?
class和struct唯一的区别就是默认访问权限不同,class默认访问权限为private,而struct则是public。P241
练习题7.18
封装是何含义?有什么用处?
封装就是定义一系列的接口,对用户隐藏实现细节,用户在使用时只需要调用接口就可以。P240
练习题7.19
在Person类中,将哪些成员声明成public?哪些声明成private?
Person类的构造函数和获取信息等函数应该设置为public,成员数据设置为private。因为构造函数和获取信息的函数需要在类外进行调用,而成员数据可以封装成接口,不需要暴露给用户。
练习题7.20
友元在什么时候有用?请分别列举出使用友元的利弊。
友元是类提供给非成员函数访问类内私有成员的一种机制。优势是:让类外函数也可以像类内成员一样方便的访问私有成员。缺点是:破坏了类的封装,写法较麻烦,必须在类内类外都进行声明。P241
练习题7.21
友元在什么时候有用?请分别列举出使用友元的利弊。
// 对Sales_data类做如下改动
class Sales_data
{
friend std::istream& read (std::istream& is, Sales_data& item);
friend std::ostream& print (std::ostream& os, const Sales_data& item);
friend Sales_data& add(Sales_data* item1, Sales_data& item2);
public:
Sales_data() = default;
Sales_data(const std::string &s) : bookNo(s){}
Sales_data(const std::string &s, unsigned n, double r) : bookNo(s), units_sold(n), revenue(r) {}
Sales_data(std::istream& is){ read(is, *this); };
std::string isbn() const { return bookNo; } // 返回ISBN编号
Sales_data& combine (const Sales_data& rhs); // 将一个Sales_data对象加到另一个上面
private:
std::string bookNo; // 对象的ISBN编号
unsigned units_sold = 0; // 售出的册数
double revenue = 0.0; // 总价格
};
std::istream& read (std::istream& is, Sales_data& item);
std::ostream& print (std::ostream& os, const Sales_data& item);
Sales_data& add(Sales_data* item1, Sales_data& item2);
练习题7.22
修改你的Person类使其隐藏实现细节。
#include <string>
#include <iostream>
class Person {
friend std::istream &read(std::istream &is, Person &person);
friend std::ostream &print(std::ostream &os, const Person &person);
public:
Person() = default;
Person(const std::string sname, const std::string saddr):name(sname), address(saddr){ }
Person(std::istream &is){ read(is, *this); }
std::string getName() const { return name; }
std::string getAddress() const { return address; }
private:
std::string name;
std::string address;
};
std::istream &read(std::istream &is, Person &person)
{
is >> person.name >> person.address;
return is;
}
std::ostream &print(std::ostream &os, const Person &person)
{
os << person.name << " " << person.address;
return os;
}
练习题7.23
编写你自己的Screen类。
#include <string>
class Screen {
public:
using pos = std::string::size_type;
Screen() = default;
Screen(pos ht, pos wd, char c):height(ht), width(wd), contents(ht*wd, c){ }
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;
std::string contents;
};
练习题7.24
给你的Screen类添加三个构造函数:一个默认构造函数;另一个构造函数接受宽和高的值,然后将contents初始化成给定数量的空白;第三个参数接受宽和高的值以及一个字符,该字符作为初始化之后屏幕的内容。
#include <string>
class Screen {
public:
using pos = std::string::size_type;
Screen() = default; // 1
Screen(pos ht, pos wd):height(ht), width(wd), contents(ht*wd, ' '){ } // 2
Screen(pos ht, pos wd, char c):height(ht), width(wd), contents(ht*wd, c){ } // 3
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;
std::string contents;
};
练习题7.25
Screen能安全的依赖拷贝和赋值操作的默认版本吗?如果能,为什么?如果不能,为什么?
可以依赖默认拷贝和赋值操作,因为类中没有分配内存的操作。
练习题7.26
将Sales_data::avg_price定义成内联函数。
在Sales_data类中的public下添加下面语句:inline double avg_price() const { return units_sold ? revenue/units_sold : 0; }
练习题7.27
给你自己的Screen类添加move、set和display函数,通过执行下面的代码检验你的类是否正确。
#include <iostream>
#include <vector>
#include <string>
#include <initializer_list>
using namespace std;
class Screen {
public:
using pos = std::string::size_type;
Screen() = default; // 1
Screen(pos ht, pos wd):height(ht), width(wd), contents(ht*wd, ' '){ } // 2
Screen(pos ht, pos wd, char c):height(ht), width(wd), contents(ht*wd, c){ } // 3
char get() const { return contents[cursor]; }
char get(pos r, pos c) const { return contents[r*width+c]; }
inline Screen& move(pos r, pos c);
inline Screen& set(char c);
inline Screen& set(pos r, pos c, char ch);
const Screen& display(std::ostream &os) const { do_display(os); return *this; }
Screen& display(std::ostream &os) { do_display(os); return *this; }
private:
void do_display(std::ostream &os) const { os << contents; }
private:
pos cursor = 0;
pos height = 0, width = 0;
std::string contents;
};
inline Screen& Screen::move(pos r, pos c)
{
cursor = r*width + c;
return *this;
}
inline Screen& Screen::set(char c)
{
contents[cursor] = c;
return *this;
}
inline Screen& Screen::set(pos r, pos c, char ch)
{
contents[r*width+c] = ch;
return *this;
}
int main()
{
Screen myScreen(6, 6, 'O');
myScreen.move(4, 0).set('X').display(std::cout);
std::cout << "\n";
myScreen.display(std::cout);
std::cout << "\n";
return 0;
}
测试
OOOOOOOOOOOOOOOOOOOOOOOOXOOOOOOOOOOO
OOOOOOOOOOOOOOOOOOOOOOOOXOOOOOOOOOOO
练习题7.28
如果move、set和display函数的返回类型不是Screen&而是Screen,则在上一个练习中会发生什么情况?
如果都返回的是Screen,则move和set都修改的是副本,原始的myScreen并没有被修改,myScreen.display(cout);语句最终打印出来还是(6, 6, ‘O’’)的值。
练习题7.29
修改程序,令move、set和display函数的返回类型是Screen,验证上题中你的猜测。
修改后:
OOOOOOOOOOOOOOOOOOOOOOOOXOOOOOOOOOOO//myScreen.move(4, 0).set('X').display(std::cout);
OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO//myScreen.display(std::cout);
练习题7.30
通过this指针使用成员的做法虽然合法,但是有点多余。讨论显式地使用指针访问成员的优缺点。
优点:
- 1、使程序意图明确,更易读;
- 2、可以使形参名和要赋值的成员名相同。
如:std::string& setName(const string& name) { this->name = name; }
缺点:有些场景下比较多余,std::string const& getName() const { return this->name; }
练习题7.31
定义一对类X和Y,其中X包含一个指向Y的指针,而Y包含一个类型位X的对象。
class X;
class Y;
class X
{
private:
Y* y = nullptr;
};
class Y
{
private:
X x;
};
练习题7.32
定义你自己的Screen和Window_mgr,其中clear是Window_mgr的成员,是Screen的友元。
// 要遵守定义友元函数的依赖关系
class Screen;
class Window_mgr
{
public:
using ScreenIndex = std::vector<Screen>::size_type;
void clear(ScreenIndex);
private:
std::vector<Screen> screens; // 这里不能调用Screen构造函数,否则报错引用了不完全类型
};
class Screen
{
friend void Window_mgr::clear(ScreenIndex);
/* Screen类的其他定义 */
};
void Window_mgr::clear(ScreenIndex i)
{
Screen &s = screens[i];
s.contents = string(s.height * s.width, ' ');
}
练习题7.33
如果我们给Screen添加一个如下所示的size成员将会发生什么情况?如果出来问题,请尝试修改它。
pos Screen::size() const
{
return height * width;
}
// 将会发生提示pos是不知道的类型。应做如下修改:
Screen::pos Screen::size() const
练习题7.34
如果我们把P256的Screen类中的pos的typedef放在类的最后一行会发生什么情况?
将会提示pos是不知道的类型。
练习题7.35
介绍下面代码的含义,说明其中的Type和initVal分别使用了哪个定义。如果代码存在错误,尝试修改它。
typedef string Type;
Type initVal();
class Exercise {
public:
typedef double Type;
Type setVal(Type); // double
Type initVal(); // double
private:
int val;
};
Type Exercise::setVal(Type parm) { // 返回的Type是string类型,形参类型是double类型
val = parm + initVal; // initVal调用的类中的
return val;
}
// 会报错Type Exercise::setVal(Type parm)匹配不到类中的函数,因为返回值与类中不一致。改为:
// initVal函数只声明未定义,也会报错。
Exercise::Type Exercise::setVal(Type parm){}
练习题7.36
介绍下面代码的含义,说明其中的Type和initVal分别使用了哪个定义。如果代码存在错误,尝试修改它。
struct X {
X (int i, int j):base(i), rem(base % j) {}
int rem, base; // 改为int base, rem;
};
// 用一个成员来初始化另一个成员,没有考虑顺序问题
练习题7.37
使用本节提供的Sales_data类,确定初始化下面的变量时分别使用了哪个构造函数,然后罗列出每个对象所有的数据成员的值。
Sales_data first_item(cin); // 使用了 Sales_data(std::istream &is) ; 数据成员值依赖输入
int main() {
Sales_data next; // 使用了Sales_data(std::string s = ""); bookNo = "", cnt = 0, revenue = 0.0
Sales_data last("9-999-99999-9"); // 使用了 Sales_data(std::string s = ""); bookNo = "9-999-99999-9", cnt = 0, revenue = 0.0
}
练习题7.38
有些情况我们希望提供cin作为接受istream&参数的构造函数的默认实参,请声明这样的函数。
可以写为:Sales_data(std::istream &is = std::cin) { read(is, *this); }
P260
练习题7.39
如果接受string的构造函数和接受istream&的构造函数都使用默认实参,这种行为合法吗?如果不?为什么?
不合法,如果都使用默认值,不提供实参,则编译器就不知道该调用哪个构造函数了。
练习题7.40
从下面的抽象概念中选择一个,思考这样的类需要哪些数据成员,提供一组合理的构造函数并阐明原因。
class Date
{
public:
Date(int y, int m, int d) : year(y), month(m), day(d) { }
void setYear(int y);
void setMonth(int m);
void setDay(int d);
int getYear();
int getMonth();
int getDay();
private:
int year;
int month;
int day;
};
练习题7.41
使用委托构造函数重新编写你的Sales_data类,给每个构造函数体添加一条语句,令其一旦执行就打印一条信息。用各种可能的方式分别创建Sales_data对象,认真研究每次输出的信息直到你确实理解了委托构造函数的执行顺序。
#include <iostream>
#include <vector>
#include <string>
#include <initializer_list>
class Sales_data {
friend std::istream &read(std::istream &is, Sales_data &item);
friend std::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 std::string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(n*p)
{ std::cout << "Sales_data(const std::string&, unsigned, double)" << std::endl; }
Sales_data() : Sales_data("", 0, 0.0f)
{ std::cout << "Sales_data()" << std::endl; }
Sales_data(const std::string &s) : Sales_data(s, 0, 0.0f)
{ std::cout << "Sales_data(const std::string&)" << std::endl; }
Sales_data(std::istream &is):Sales_data()//Sales_data(istream& is)是一个委托构造函数,它委托给了默认构造函数Sales_data()
{std::cout << "Sales_data(const std::string&)" << std::endl;};
std::string isbn() const { return bookNo; }
Sales_data& combine(const Sales_data&);
private:
inline double avg_price() const;
private:
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
inline
double Sales_data::avg_price() const
{
return units_sold ? revenue/units_sold : 0;
}
// declarations for nonmember parts of the Sales_data interface.
std::istream &read(std::istream &is, Sales_data &item);
std::ostream &print(std::ostream &os, const Sales_data &item);
Sales_data add(const Sales_data &lhs, const Sales_data &rhs);
using namespace std;
int main()
{
cout << "1. default way: " << endl;
cout << "----------------" << endl;
Sales_data s1;
cout << "\n2. use std::string as parameter: " << endl;
cout << "----------------" << endl;
Sales_data s2("CPP-Primer-5th");
cout << "\n3. complete parameters: " << endl;
cout << "----------------" << endl;
Sales_data s3("CPP-Primer-5th", 3, 25.8);
cout << "\n4. use istream as parameter: " << endl;
cout << "----------------" << endl;
Sales_data s4(std::cin);
return 0;
}
测试:
1. default way:
----------------
Sales_data(const std::string&, unsigned, double)
Sales_data()
2. use std::string as parameter:
----------------
Sales_data(const std::string&, unsigned, double)
Sales_data(const std::string&)
3. complete parameters:
----------------
Sales_data(const std::string&, unsigned, double)
4. use istream as parameter:
----------------
Sales_data(const std::string&, unsigned, double)
Sales_data()
Sales_data(const std::string&)
练习题7.43
假定有一个类名为NoDefault的类,它有一个接受int的构造函数,但是没有默认构造函数。定义类c,c有一个NoDefault类型的成员,定义c的默认构造函数。
class NoDefault {
public:
NoDefault(int i) { }
};
class C {
public:
C() : def(0) { } // 定义C的构造函数.
private:
NoDefault def;
};
练习题7.44
下面这条声明合理吗?为什么?
vector vec(10);
非法,因为vector的10个元素没有被初始化,因此需要默认初始化,而NoDefault类型没有提供默认构造函数
练习题7.45
如果上题中定义的vector类型是C,则声明合法吗?
合法,因为C类提供了默认构造函数。
练习题7.46
下面哪种论断是不正确的?为什么?
1.一个类必须至少提供一个构造函数。
2.默认构造函数是参数列表为空的构造函数。
3.如果对于类来说不存在有意义的默认值,则类不应该提供默认构造函数。
4.如果类没有定义默认构造函数,则编译器将为其生成一个并把每个数据成员初始化成相应类型的默认值。
均不正确:
- 1.类可以不提供构造函数,编译器会提供一个默认构造函数。
- 2.默认构造函数为没有初始化列表(而不是参数列表为空)的对象提供默认初始值,为成员提供默认值的构造函数也称为默认构3.造函数。
- 3.类应该提供默认构造函数。
- 4.只有当类没有定义任何构造函数的时候,编译器才会定义默认构造函数。
练习题7.47
说明接受一个string参数的Sales_data构造函数是否应该是explicit的,并解释这样做的优缺点。
Sales_data类的构造函数应该是explicit的。
优点:
- 保证用户能按照类设计者的初衷进行初始化。
缺点:
- 当只有一个参数时,要进行初始化再使用,没有隐式转换的写法简洁。
练习题7.48
假定Sales_data的构造函数不是explicit的,下述定义将会执行什么操作?
string null_isbn("9-99-9999-9");
Sales_data item1(null_isbn); // 用string类型的null_isbn直接初始化item1。
Sales_data item2("9-99-9999-9"); // 用字符串初始化item2,为Sales_data 类型。
练习题7.49
对于combine函数的三种不同声明,当我们调用i.combine(s)时分别发生什么情况?其中i是一个Sales_data,而s是一个string对象。
(a)Sales_data &combine(Sales_data);
正常初始化,将s转化成Sales_data类型。
(b)Sales_data &combine(Sales_data&);
报错:error: invalid initialization of non-const reference of type ‘Sales_data&’ from an rvalue of type ‘Sales_data’,string不能转化为Sales_data类型的引用。
(c)Sales_data &combine(const Sales_data&) const;
报错:error: assignment of member ‘Sales_data::units_sold’ in read-only object,声明最后的const会禁止函数对值做出改变。
练习题7.50
确定在你的person类中是否有一些构造函数应该是explicit的。
explicit Person (std::istream& is) { readPerson(is, *this); }
// 只接受一个参数的构造函数可以是explicit
练习题7.51
vector将其单参数的构造函数定义成explicit的,而string则不是,你觉得原因何在?
https://blog.csdn.net/misayaaaaa/article/details/56479195
“定义为explicit是为了防止隐式的类型转换。”
int getSize(const std::vector<int>&);
//这样的使用是否显得比较迷惑
getSize(34);
练习题7.52
使用2.6.1节(P64)的Sales_data类,解释下面的初始化过程。如果存在问题,尝试修改它。
Sales_data item = {"978-059035", 15, 29.98};
// 要使用这种初始化方式,要求类必须是聚合类。因此Sales_data类需要改成如下形式:
struct Sales_data
{
std::string bookNo;
unsigned int unit_sold;
double revenue;
};
练习题7.53
定义你自己的Debug。
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_hw (bool b) { hw = b; }
void set_io (bool b) { io = b; }
void set_other (bool b) { other = b; }
private:
bool hw; // 硬件错误
bool io; // io错误
bool other; // 其它错误
};
练习题7.54
Debug中以set_开头的成员应该被声明为constexpr吗?如果不,为什么?
在c++11中声明函数是constexpr必须满足以下条件(P267):
- 返回值和参数必须是Literal类型
- 函数体必须只包含一个return语句
- 函数提可以包含其他的语句,但是这些语句不能在运行期起作用
- 函数可以不返回常量,但是在调用的时候实参必须传入常量表达式
因此,如果按照c++11的标准,set_开头的成员函数不能被声明为constexpr。
报错信息:error: assignment of member ‘Debug::hw’ in read-only object
error: invalid return type ‘void’ of constexpr function ‘constexpr void Debug::set_hw(bool) const’
练习题7.55
7.5.5节(P256)的Data类是字面值常量类吗?为什么?
数据成员都是字面值类型的聚合类是字面值常量类。 但Data类的数据成员不一定是字面值类型,使用变量或表达式也可以进行初始化。
练习题7.56
什么是类的静态成员?它有何优点?静态成员与普通成员有何区别?
类的静态成员与类本身直接相关,而不是与类的各个对象关联。P268
优点:每个对象都不需要单独存储静态成员变量,一旦静态成员改变了,则每个对象都可以使用新的值。
区别:类的静态成员属于类本身,在类加载时就会分配内存,可以通过类名直接进行访问。
普通成员属于类的对象,只有在类对象产生时才会分配内存。只能通过对象去访问。
练习题7.57
编写你自己的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();
};
练习题7.58
下面的静态数据成员的声明和定义有错吗?请解释原因。
// example.h
class Example
{
public:
static double rate = 6.5; // 错误,静态成员类内初始化应该是一个const表达式。
// 改为 static constexpr double rete = 6.5;
static const int vecSize = 20;
static vector<double> vec(vecSize); // vector不需要在类内就定义大小 P270
// 改为static vector<double> vec;
}
// examplec.cpp
#include "example.h"
double Example::rate;
vector<double> Example::vec;