C++ Primer 练习题 1

Posted Arthur的仓库

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ Primer 练习题 1相关的知识,希望对你有一定的参考价值。

练习7.1

使用2.6.1节定义的Sales_data类为1.6节的交易处理程序编写一个新版本。

#include <iostream>#include <string>
using namespace std;
struct Sales_data{ string bookNo; unsigned units_sold = 0; double revenue = 0.0;};
int main() { Sales_data current; if (cin >> current.bookNo >> current.units_sold >> current.revenue) { Sales_data next; while (cin >> next.bookNo >> next.units_sold >> next.revenue) { if(current.bookNo == next.bookNo) { current.units_sold += next.units_sold; current.revenue += next.revenue; } else { cout << current.bookNo << " " << current.units_sold << " " << current.revenue << endl; current = next; } } cout << current.bookNo << " " << current.units_sold << " " << current.revenue << endl; } else { cout << "NO data?" << endl; }
return 0;}

练习7.2

曾在2.6.2节的练习中编写了一个Sales_data类,请向这个类添加combine函数和isbn成员。

#include <string>
struct Sale_data{ std::string bookNo; std::string bookName; unsigned units_sold = 0;    double revenue = 0.0;
std::string isbn() const { return bookNo; } Sale_data& combine(const Sale_data&);};
Sale_data& Sale_data::combine(const Sale_data &rhs) { units_sold += rhs.units_sold; revenue += rhs.revenue; return *this;}

练习7.3

修改7.1.1节的交易处理程序,令其使用这些成员。

#include <iostream>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 { std::cerr << "No data?!" << std::endl; return -1; } return 0;}

练习7.4

#include <string>
struct Person { std::string name; std::string address;};

练习7.5

struct Person { std::string name; std::string address;
std::string getName() const { return name; } std::string getAddress() const { return address; }};

应该是const的。因为常量的Person对象也需要使用这些函数操作。

练习7.6

对于函数addreadprint,定义你自己的版本。

#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 functionsstd::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;}

练习7.8

为什么read函数将其Sales_data参数定义成普通的引用,而print函数将其参数定义成常量引用?

因为read函数会改变对象的内容,而print函数不会。

练习7.9

对于7.1.2节练习中代码,添加读取和打印Person对象的操作。

#include <string>#include <iostream>
struct Person { std::string name; std::string address;
std::string getName() const { return name; } std::string getAddress() const { return address; }};
std::istream &read(std::istream &is, Person &p) { is >> p.name >> p.address; return is;}
std::ostream &print(std::ostream &os, const Person &p) { std::cout << p.name << " " << p.address; return os;}

练习7.10

在下面这条if语句中,条件部分的作用是什么?

if (read(read(cin, data1), data2))

read函数的返回值是istream对象,先从标准输入流中向data1写入数据,如果这个动作成功,则继续向data2中写入数据。

练习7.11 :

在你的Sales_data类中添加构造函数, 然后编写一段程序令其用到每个构造函数。

Sales_data.h

#include <string>#include <iostream>
struct Sales_data;
std::istream &read(std::istream&, Sales_data&);std::ostream &print(std::ostream&, const Sales_data&);Sales_data add(const Sales_data&, const Sales_data&);
struct Sales_data { Sales_data() = default; Sales_data(std::string bN, unsigned n, double p) : bookNo(bN), units_sold(n), revenue(n * p) { } Sales_data(std::string bN) : bookNo(bN) { } Sales_data(const Sales_data&); Sales_data(std::istream&);
std::string const& 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(const Sales_data &s) { bookNo = s.bookNo; units_sold = s.units_sold; revenue = s.revenue;}
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;}
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;}

main.cpp

#include <iostream>#include "Sales_data.h"
int main() { Sales_data s1; print(std::cout, s1); std::cout << std::endl;
Sales_data s2("0-201-78345-X", 3, 20.00); print(std::cout, s2); std::cout << std::endl;
Sales_data s3("0-201-78345-X"); print(std::cout, s3); std::cout << std::endl;
Sales_data s4(s2); print(std::cout, s4); std::cout << std::endl;
Sales_data s5(std::cin); print(std::cout, s5); std::cout << std::endl;}

练习7.12

把只接受一个istream作为参数的构造函数移到类的内部。

Sales_data(std::istream &is) { read(is, *this); }

练习7.13

使用istream构造函数重写第229页的程序。

#include <iostream>#include "Sales_data.h"
using namespace std;
int main(){ Sales_data total(cin); if (!total.isbn().empty()) { while (cin) { Sales_data trans(cin); if (!cin) break; if (total.isbn() == trans.isbn()) { total.combine(trans); } else { print(cout, total); cout << endl; total = trans; } } print(cout, total); cout << endl; } else { std::cerr << "No data?!" << std::endl; return -1; } return 0;}

练习7.14

编写一个构造函数,令其用我们提供的类内初始值显式地初始化成员。

Sales_data() : bookNo(std::string()), units_sold(0), revenue(0.0) { }

练习7.15

为你的Person类添加正确的构造函数。

Person.h

#include <string>#include <iostream>
struct Person;std::istream &read(std::istream &, Person &);std::ostream &print(std::ostream &, const Person &);
struct Person {
Person() = default; Person(const std::string &sname, const std::string &saddress) : name(sname), address(saddress) { } Person(const std::string &sname) : name(sname) { } Person(const Person &p) : name(p.name), address(p.address) { } Person(std::istream &);
std::string getName() const { return name; } std::string getAddress() const { return address; }
std::string name; std::string address;};
Person::Person(std::istream &is) { read(is, *this);}
std::istream &read(std::istream &is, Person &p) { is >> p.name >> p.address; return is;}
std::ostream &print(std::ostream &os, const Person &p) { std::cout << p.name << " " << p.address; return os;}

main.cpp

#include <iostream>#include "Person.h"
int main(){ Person p; print(std::cout, p); std::cout << std::endl;
Person p1("Charles", "Fengcheng 1st Road, Xi'an City, Shaanxi Province"); print(std::cout, p1); std::cout << std::endl; Person p2("Charles"); print(std::cout, p2); std::cout << std::endl;
Person p3(p1); print(std::cout, p3); std::cout << std::endl; Person p4(std::cin); print(std::cout, p4); std::cout << std::endl; return 0;}

练习7.16

在类的定义中对于访问说明符出现的位置和次数有限定吗?如果有,是什么?什么样的成员应该定义在public说明符之后?什么样的成员应该定义在private说明符之后?

在类的定义中对于访问说明符出现的位置和次数没有限定

每个访问说明符指定了接下来的成员的访问级别,其有效范围直到出现下一个访问说明符或者达到类的结尾处为止。

如果某个成员能够在整个程序内都被访问,那么它应该定义为public; 如果某个成员只能在类内部访问,那么它应该定义为private

练习7.17

使用classstruct时有区别吗?如果有,是什么?

classstruct的唯一区别是默认的访问级别不同。

练习7.18

封装是何含义?它有什么用处?

将类内部分成员设置为外部不可见,而提供部分接口给外面,这样的行为叫做封装。

用处:

  • 1.确保用户的代码不会无意间破坏封装对象的状态。

  • 2.被封装的类的具体实现细节可以随时改变,而无需调整用户级别的代码。

练习7.19

在你的Person类中,你将把哪些成员声明成public的?哪些声明成private的?解释你这样做的原因。

构造函数、getName()getAddress()函数将设为public。 name和 address 将设为private。函数是暴露给外部的接口,因此要设为public;而数据则应该隐藏让外部不可见。

练习7.20

友元在什么时候有用?请分别举出使用友元的利弊。

当其他类或者函数想要访问当前类的私有变量时,这个时候应该用友元。

利:

与当前类有关的接口函数能直接访问类的私有变量。

弊:

牺牲了封装性与可维护性。

练习7.21

修改你的Sales_data类使其隐藏实现的细节。你之前编写的关于Sales_data操作的程序应该继续使用,借助类的新定义重新编译该程序,确保其正常工作。

#include <string>#include <iostream>
// 统一在类的外部进行独立声明:class Sales_data;Sales_data add (const Sales_data, const Sales_data&);std::istream& read(std::istream&, Sales_data&);std::ostream& print(std::ostream&, const Sales_data&);
class Sales_data { friend std::istream &read(std::istream&, Sales_data&); friend std::ostream &print(std::ostream&, const Sales_data&); friend Sales_data add(const Sales_data&, const Sales_data&);
public: 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) { read(is, *this); }
std::string isbn() const { return bookNo; }; Sales_data& combine(const Sales_data&);
private: 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;}
// friend functionsstd::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.22

修改你的Person类使其隐藏实现的细节。

#include <string>#include <iostream>
class Person;std::istream& read(std::istream, Person&);std::ostream& print(std::ostream&, const Person&);
class Person { friend std::istream &read(std::istream&, Person&); friend std::ostream &print(std::ostream&, const Person&);
public: Person() = default; Person(const std::string &sname, const std::string &saddress) : name(sname), address(saddress) { } Person(const std::string &sname) : name(sname) { } Person(const Person &p) : name(p.name), address(p.address) { } Person(std::istream &);
std::string getName() const { return name; } std::string getAddress() const { return address; }
private: std::string name; std::string address;};
Person::Person(std::istream &is) { read(is, *this);}
std::istream &read(std::istream &is, Person &p) { is >> p.name >> p.address; return is;}
std::ostream &print(std::ostream &os, const Person &p) { std::cout << p.name << " " << p.address; return os;}

练习7.23

编写你自己的Screen类型。

#include <iostream>#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]; } Screen& move(pos r, pos c) { cursor = r*width+c; return *this; } private: mutable pos cursor = 0; pos height = 0, width = 0; std::string contents;};

练习7.24

给你的Screen类添加三个构造函数:一个默认构造函数;另一个构造函数接受宽和高的值,然后将contents初始化成给定数量的空白;第三个构造函数接受宽和高的值以及一个字符,该字符作为初始化后屏幕的内容。

#include <iostream>#include <string>
class Screen {public: using pos = std::string::size_type; Screen() = default; Screen(pos ht, pos wd) : height(ht), width(wd), contents(ht*wd, ' ') { } 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]; } Screen& move(pos r, pos c) { cursor = r*width+c; return *this; }
private: mutable pos cursor = 0; pos height = 0, width = 0; std::string contents;};

练习7.25

Screen能安全地依赖于拷贝和赋值操作的默认版本吗?如果能,为什么?如果不能?为什么?

能。 Screen的成员只有内置类型和string,因此能安全地依赖于拷贝和赋值操作的默认版本。

管理动态内存的类则不能依赖于拷贝和赋值操作的默认版本,而且也应该尽量使用stringvector来避免动态管理内存的复杂性。

练习7.26

Sales_data::avg_price定义成内联函数。

在头文件中加入:

inline double Sales_data::avg_price() const{ return units_sold ? revenue/units_sold : 0;}

练习7.27

给你自己的Screen类添加moveset 和display函数,通过执行下面的代码检验你的类是否正确。

Screen myScreen(5, 5, 'X');myScreen.move(4, 0).set('#').display(cout);cout << "\n";myScreen.display(cout);cout << "\n";
#include <iostream>#include <string>
class Screen {public: using pos = std::string::size_type; Screen() = default; Screen(pos ht, pos wd) : height(ht), width(wd), contents(ht*wd, ' ') { } 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]; } Screen& move(pos r, pos c) { cursor = r*width+c; return *this; } Screen& set(pos r, pos c, char ch) { contents[r*width+c] = ch; return *this; } Screen& set(char ch) { contents[cursor] = ch; return *this; } Screen& display(std::ostream& os) { os << contents; return *this; } const Screen& display(std::ostream& os) const { os << contents; return *this; }
private: pos cursor = 0; pos height = 0, width = 0; std::string contents;};

练习7.28

如果movesetdisplay函数的返回类型不是Screen& 而是Screen,则在上一个练习中将会发生什么?

如果返回类型是Screen,那么move返回的是*this的一个副本,因此set函数只能改变临时副本而不能改变myScreen的值。

练习7.29

修改你的Screen类,令movesetdisplay函数返回Screen并检查程序的运行结果,在上一个练习中你的推测正确吗?

推测正确。

#with '&'XXXXXXXXXXXXXXXXXXXX#XXXXXXXXXXXXXXXXXXXXXXXX#XXXX ^# without '&'XXXXXXXXXXXXXXXXXXXX#XXXXXXXXXXXXXXXXXXXXXXXXXXXXX                    ^

练习7.30

通过this指针使用成员的做法虽然合法,但是有点多余。讨论显示使用指针访问成员的优缺点。

优点:

程序的意图更明确

函数的参数可以与成员同名,如

void setAddr(const std::string &addr) { this->addr = addr; }

缺点:

有时候显得有点多余,如

std::string getAddr() const { return this->addr; }

练习7.31

定义一对类XY,其中X包含一个指向Y的指针,而Y包含一个类型为X的对象。

class Y;
class X{ Y* y = nullptr; };
class Y{ X x;};

练习7.32

定义你自己的ScreenWindow_mgr,其中clearWindow_mgr的成员,是Screen的友元。

#include <iostream>#include <string>#include <vector>
class Screen;
class Window_mgr{public: using ScreenIndex = std::vector<Screen>::size_type; inline void clear(ScreenIndex);
private: std::vector<Screen> screens;};
class Screen { friend void Window_mgr::clear(ScreenIndex);public: using pos = std::string::size_type; Screen() = default; Screen(pos ht, pos wd) : height(ht), width(wd), contents(ht*wd, ' ') { } 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]; } Screen& move(pos r, pos c) { cursor = r*width+c; return *this; } Screen& set(pos r, pos c, char ch) { contents[r*width+c] = ch; return *this; } Screen& set(char ch) { contents[cursor] = ch; return *this; } Screen& display(std::ostream& os) { os << contents; return *this; } const Screen& display(std::ostream& os) const { os << contents; return *this; }
private: pos cursor = 0; pos height = 0, width = 0; std::string contents;};
inline void Window_mgr::clear(ScreenIndex i) { Screen& s = screens[i]; s.contents = std::string(s.height*s.width,' '); };

以上是关于C++ Primer 练习题 1的主要内容,如果未能解决你的问题,请参考以下文章

C++ Primer 0x07 练习题解

C++ Primer 0x0D 练习题解

C++ Primer 0x04 练习题解

C++ Primer 0x02 练习题解

C++ Primer 0x08 练习题解

C++ Primer Plus编程练习答案——第13章