C++Primer第五版习题答案详解(七) 发表于 2022-07-23 | 更新于 2024-11-18
| 字数总计: 4.5k | 阅读时长: 22分钟 | 阅读量:
答案目录
第7章 类
C++ 中保留了C语言的 struct 关键字,并且加以扩充。在C语言中,struct 只能包含成员变量,不能包含成员函数。
而在C++中,struct 类似于 class,既可以包含成员变量,又可以包含成员函数。
C++中的 struct 和 class 基本是通用的,唯有几个细节不同:
使用 class 时,类中的成员默认都是 private 属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的。(最本质的区别)
class 继承默认是 private 继承,而 struct 继承默认是 public 继承(《C++继承与派生》一章会讲解继承)
class 可以使用模板,而 struct 不能。
练习7.1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> #include <string> #include <vector> using namespace std;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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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.4
1 2 3 4 class Person { std::string name; std::string address; };
练习7.5
要使用const,因为这些函数并不改变它调用的对象的内容
1 2 3 4 5 6 7 class Person {public : std::string name; std::string addr; std::string getName () const { return name; } std::string getAddr () const { return addr; } };
练习7.6,7.7
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 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 ; }; 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; } 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
因为print函数不会改变对象的值,但是read函数则会改变对象内容
练习7.9
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 struct Person { std::string const & getName () const { return name; } std::string const & getAddress () const { return address; } std::string name; std::string address; }; 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; } int main () { return 0 ; }
练习7.10
是否成功读入data1,data2
练习7.11
1 2 3 4 5 6 7 8 9 10 11 12 13 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 ; };
练习7.12
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct Sales_data ;std::istream &read (std::istream&, Sales_data&) ;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) { read (is, *this ); } std::string isbn () const { return bookNo; }; Sales_data& combine (const Sales_data&) ; std::string bookNo; unsigned units_sold = 0 ; double revenue = 0.0 ; };
练习7.13
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Sales_data total (cin) ; if (cin) { Sales_data trans (cin) ; do { if (total.isbn () == trans.isbn ()) total.combine (trans); else { print (cout, total) << endl; total = trans; } }while (read (cin, trans)); print (cout, total)<<endl; } else { cerr << "No data?!" <<endl; }
练习7.14
1 Sales_data () : bookNo ("" ), units_sold (0 ) , revenue (0 ){ }
练习7.15
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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; };
练习7.16
一个类对访问说明符出现的次数和位置并没有严格的限定。构造函数和接口函数定义在public之后,而数据成员和部分成员函数定义在private后面
练习7.17
class和struct唯一的区别就是默认访问权限不同,class默认访问权限为private,而struct则是public
练习7.18
封装就是定义一系列的接口,对用户隐藏实现细节,用户在使用时只需要调用接口就可以
练习7.19
Person类的构造函数和获取信息等函数应该设置为public,成员数据设置为private
因为构造函数和获取信息的函数需要在类外进行调用,而成员数据可以封装成接口,不需要暴露给用户
练习7.20
友元是类提供给非成员函数访问类内私有成员的一种机制
优势是:让类外函数也可以像类内成员一样方便的访问私有成员
缺点是:破坏了类的封装,写法较麻烦,必须在类内类外都进行声明
练习7.21
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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 () = 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 ; };
练习7.22
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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; };
练习7.23
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #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]; } private : pos cursor = 0 ; pos height = 0 , width = 0 ; std::string contents; };
练习7.25
只有内置类型和string类型可以依赖于操作的默认版本
练习7.26
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <string> #include <iostream> 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 () = 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 : 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 ; } 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) ;
练习7.27
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 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]; } 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 (5 ,5 ,'x' ) ; myscreen.move (4 , 0 ).set ('#' ).display (cout); cout << "\n" ; myscreen.display (cout); cout << "\n" ; }
练习7.28
若函数返回类型变为Screen,则返回的是对象的副本,函数的操作只能添加于对象的副本上,对象的本身并没有改变。
此题中,myScreen本身并不会被三个函数所改变,所以不会输出“#”
练习7.29
1 2 3 4 5 6 7 xxxxxxxxxxxxxxxxxxxx#xxxx xxxxxxxxxxxxxxxxxxxx#xxxx xxxxxxxxxxxxxxxxxxxx#xxxx xxxxxxxxxxxxxxxxxxxxxxxxx
练习7.30
优点:
1、使程序意图明确,更易读;
2、可以使形参名和要赋值的成员名相同。
如:std::string& setName(const string& name) { this->name = name; }
缺点:有些场景下比较多余
std::string const& getName() const { return this->name; }
练习7.31
1 2 3 4 5 6 7 8 9 class Y ;class X { Y* y = nullptr ; }; class Y { X x; };
练习7.32
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include <vector> #include <string> #include <iostream> 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]; } 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 void Window_mgr::clear (ScreenIndex i) { if (i >= screens.size ()) return ; Screen &s = screens[i]; s.contents = std::string (s.height * s.width, ' ' ); } 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 ; }
练习7.33
error: unknown type name ‘pos’
pos在类中声明定义,在外部使用时需要声明作用域
1 2 3 4 Screen::pos Screen::size () const { return height*width; }
练习7.34
将会提示pos是不知道的类型。
练习7.35
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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; } Exercise::Type Exercise::setVal (Type parm) {}
练习7.36
1 2 3 4 5 struct X { X (int i, int j):base (i), rem (base % j) {} int rem, base; };
练习7.37
1 2 3 4 5 6 7 Sales_data first_item (cin) ; int main () { Sales_data next; Sales_data last ("9-999-99999-9" ) ; }
练习7.38
1 Sales_data (std::istream &is = std::cin) { read (is, *this ); }
练习7.39
不合法,如果都使用默认值,不提供实参,则编译器就不知道该调用哪个构造函数了
练习7.40
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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
1 2 3 4 5 6 7 8 9 10 11 int main () { cout << "----------- 1. default: " << endl; Sales_data s1 () ; cout << "----------- 2. init bookNo" << endl; Sales_data s2 ("999" ) ; cout << "----------- 3.use cin init" << endl; Sales_data s3 (cin) ; cout << "----------- 4. use three parameter init: " << endl; Sales_data s4 ("s01-999" , 2 , 59.8 ) ; }
输出结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 ----------- 1. default : Sales_data (const std::string &s, unsigned n, double r)default ----------- 2. init bookNo Sales_data (const std::string &s, unsigned n, double r) Sales_data (const std::string &s) ----------- 3.use cin init Sales_data (const std::string &s, unsigned n, double r) default iso-99 2 59.2 Sales_data (std::istream& is) ----------- 4. use three parameter init: Sales_data(const std::string &s, unsigned n, double r)
练习7.43
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <vector> class NoDefault {public : NoDefault (int i) { } }; class C {public : C () : def (0 ) { } private : NoDefault def; }; int main () { C c; std::vector<C> vec (10 ) ; return 0 ; }
练习7.44
非法,因为vector的10个元素没有被初始化,因此需要默认初始化,而NoDefault类型没有提供默认构造函数
练习7.45
合法,因为C类提供了默认构造函数
练习 7.46
以上论断都不正确:
类可以不提供构造函数,编译器会提供一个默认构造函数。
默认构造函数为没有初始化列表(而不是参数列表为空)的对象提供默认初始值,为成员提供默认值的构造函数也称为默认构造函数。
类应该提供默认构造函数。
只有当类没有定义任何构造函数的时候,编译器才会定义默认构造函数。
练习7.47
Sales_data类的构造函数应该是explicit的
优点:保证用户能按照类设计者的初衷进行初始化
缺点:当只有一个参数时,要进行初始化再使用,没有隐式转换的写法简洁
练习7.48
1 2 3 string null_isbn ("9-99-9999-9" ) ;Sales_data item1 (null_isbn) ; string item2 ("9-99-9999-9" ) ;
都不会有任何问题,如若没有Salesdata在前,则结果会不一样,因为皆显示地声明了属于Salesdata类
练习7.49
(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类型的引用
©Sales_data &combine(const Sales_data&) const;
//报错:error: assignment of member ‘Sales_data::units_sold’ in read-only object,声明最后的const会禁止函数对值做出改变。
练习7.50
1 2 explicit Person (std::istream& is) { readPerson (is, *this ); }
练习7.51
引用github上的答案
比如这样的函数:
1 int getSize (const std::vector<int >&) ;
如果 vector 没有将其单参数构造函数定义为 explicit,我们可以使用如下函数:
What is this mean? It’s very confused.
但是 std::string 是不同的。 通常我们使用 std::string 来替换 const char *(C 语言),所以当我们调用这样的函数时:
1 2 void setYourName (std::string) ; setYourName ("pezy" );
it is very natural.
练习7.52
1 2 3 4 5 6 7 8 Sales_data item = {"978-059035" , 15 , 29.98 }; struct Sales_data { std::string bookNo; unsigned int unit_sold; double revenue; };
练习7.53
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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; bool other; };
练习7.54
在c++11中声明函数是constexpr必须满足以下条件:
返回值和参数必须是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’
但c++14好像取消了一些限制,因此c++14编译不报错。
练习7.55
数据成员都是字面值类型的聚合类是字面值常量类。 但Data类的数据成员不一定是字面值类型,使用变量或表达式也可以进行初始化。
练习7.56
类的静态成员与类本身直接相关,而不是与类的各个对象关联。
优点:每个对象都不需要单独存储静态成员变量,一旦静态成员改变了,则每个对象都可以使用新的值。
区别:类的静态成员属于类本身,在类加载时就会分配内存,可以通过类名直接进行访问。
普通成员属于类的对象,只有在类对象产生时才会分配内存。只能通过对象去访问。
练习7.57
1 2 3 4 5 6 7 8 9 10 11 12 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Example { public : static double rate = 6.5 ; static const int vecSize = 20 ; static vector<double > vec (vecSize) ; } #include "example.h" double Example::rate;vector<double > Example::vec;