C++ Primer第5版 第七章课后练习答案

练习7.1

struct Sales_data {
    string bookNo;
    unsigned units_sold = { 0 };
    double revenue = { 0.0 };
};

int main(int argc, char* argv[])
{   
    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;
                total.units_sold += trans.units_sold;
            }
            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?!" << endl;
        return -1;
    }
    return 0;
}

练习7.2

struct Sales_data {
    string bookNo;
    unsigned units_sold = { 0 };
    double revenue = { 0.0 };

    string isbn() { return bookNo; }
    Sales_data& combine(const Sales_data& rhs) {
        units_sold += rhs.units_sold;
        revenue += rhs.revenue;
        return *this;
    }
};

练习7.3

int main(int argc, char* argv[])
{   
    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?!" << endl;
        return -1;
    }
    return 0;
}

练习7.4

#ifndef PERSON_H_
#define PERSON_H_
#include <string>
using std::string;
struct Person {
    string name;
    string address;
};
#endif // !PERSON_H_

练习7.5

struct Person {
    string name;
    string address;
    const string isname() { return name; }
    const string isaddress() { return address; }
};

应该是const的,因为我们不需要修改返回值

练习7.6

Sales_data add(Sales_data& lhs, Sales_data& rhs) {
    Sales_data sum = lhs;
    sum.combine(rhs);
    return sum;
}

istream &read(istream& is, Sales_data& it) {
    is >> it.bookNo >> it.units_sold >> it.revenue;
    return is;
}

ostream& print(ostream& os, Sales_data& it) {
    os << it.isbn() << " " << it.units_sold << " " << it.revenue << endl;
    return os;
}

练习7.7

int main(int argc, char* argv[])
{   
    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);
                total = trans;
            }
        }
        print(cout, total);
    }
    else {
        std::cerr << "No Data?!" << endl;
        return -1;
    }
    return 0;
}

练习7.8

print应该是常量引用的,因为我们不需要修改传入值

练习7.9

struct Person {
    string name;
    string address;
    const string isname() { return name; }
    const string isaddress() { return address; }
};

istream& read(istream& is, Person& it) {
    is >> it.name >> it.address;
    return is;
}

ostream& print(ostream& os, Person& it) {
    os << it.isname() << " " << it.isaddress() << endl;
    return os;
}

练习7.10

判断读操作对象是否符合要求

练习7.11

struct Sales_data {
    string bookNo;
    unsigned units_sold = { 0 };
    double revenue = { 0.0 };

    string isbn() { return bookNo; }
    Sales_data& combine(const Sales_data& rhs) {
        units_sold += rhs.units_sold;
        revenue += rhs.revenue;
        return *this;
    }
    Sales_data(string bn, unsigned us, double re) :bookNo(bn), units_sold(us), revenue(re) {}
    Sales_data() = default;
};
int main(int argc, char* argv[])
{   
    Sales_data total("",0,0);
    print(cout, total);
    return 0;
}

练习7.12

struct Sales_data {
    string bookNo;
    unsigned units_sold = { 0 };
    double revenue = { 0.0 };

    string isbn() { return bookNo; }
    Sales_data& combine(const Sales_data& rhs) {
        units_sold += rhs.units_sold;
        revenue += rhs.revenue;
        return *this;
    }
    Sales_data(string bn, unsigned us, double re) :bookNo(bn), units_sold(us), revenue(re) {}
    Sales_data() = default;
    Sales_data(istream& is) { read(is, *this); }
};

练习7.13

int main(int argc, char* argv[])
{
    Sales_data total(cin);
    if (cin) {
        Sales_data trans(cin);
        do{
            if (total.bookNo == trans.bookNo) {
                total.revenue += trans.revenue;
                total.units_sold += trans.units_sold;
            }
            else {
                cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl;
                total = trans;
            }
        } while (read(cin, trans));
        cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl;
    }
    else {
        std::cerr << "No Data?!" << endl;
        return -1;
    }
    return 0;
}

练习7.14

Sales_data(string bn, unsigned us, double re) :bookNo(bn), units_sold(us = 0), revenue(re = 0.0) {};

练习7.15

struct Person {
    string name;
    string address;
    const string isname() { return name; }
    const string isaddress() { return address; }
    Person() = default;
    Person(string name, string address) :name(name), address(address) {}
};

练习7.16

没有限定。在整个程序内可被访问到的成员应定义在public说明符之后;可以被类的成员访问但不能被使用该类代码访问的应该定义在private说明符之后。

练习7.17

有区别,struct的默认访问权限是public,class的默认访问权限是class

练习7.18

封装:使用函数指针把属性与方法封装到结构体中

练习7.19

成员函数声明成public,成员变量声明成private

练习7.20

类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。

优点:能访问私有成员

缺点:破坏封装性

练习7.21

class Sales_data {
    friend istream& read(istream& is, Sales_data& it);
    friend ostream& print(ostream& os, Sales_data& it);
private:
    string bookNo;
    unsigned units_sold = { 0 };
    double revenue = { 0.0 };
public:
    const string& isbn() { return bookNo; }
    Sales_data& combine(const Sales_data& rhs) {
        units_sold += rhs.units_sold;
        revenue += rhs.revenue;
        return *this;
    }
    Sales_data(string bn, unsigned us, double re) :bookNo(bn), units_sold(us = 0), revenue(re = 0.0) {};
    Sales_data() = default;
    Sales_data(istream& is) { read(is, *this); }
};

Sales_data add(Sales_data& lhs, Sales_data& rhs) {
    Sales_data sum = lhs;
    sum.combine(rhs);
    return sum;
}

istream &read(istream& is, Sales_data& it) {
    is >> it.bookNo >> it.units_sold >> it.revenue;
    return is;
}

ostream& print(ostream& os, Sales_data& it) {
    os << it.isbn() << " " << it.units_sold << " " << it.revenue << endl;
    return os;
}

练习7.22

class Person {
    friend istream& read(istream& is, Person& it);
    friend ostream& print(ostream& os, Person& it);
private:
    string name;
    string address;
public:
    const string isname() { return name; }
    const string isaddress() { return address; }
    Person() = default;
    Person(string name, string address) :name(name), address(address) {}
};

istream& read(istream& is, Person& it) {
    is >> it.name >> it.address;
    return is;
}

ostream& print(ostream& os, Person& it) {
    os << it.isname() << " " << it.isaddress() << endl;
    return os;
}

练习7.23

#ifndef SCREEN_H_
#define SCREEN_H_
#include<string>
class Screen {
public:
    using pos = std::string::size_type;
private:
    pos cursor = 0;
    pos height = 0, width = 0;
    std::string contents;
};
#endif // !SCREEN_H_

练习7.24

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) {}
private:
    pos cursor = 0;
    pos height = 0, width = 0;
    std::string contents;
};

练习7.25

可以,因为只有内置类型和可变类型如vector类型,string类型可以依赖于操作的默认版本

练习7.26

在函数体外定义或者类内声明的返回值前添加inline关键字即可

练习7.27

#ifndef SCREEN_H_
#define SCREEN_H_
#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) {}
    Screen& set(char);
    Screen& set(pos, pos,char);
    Screen& move(pos, pos);
    Screen& display(std::ostream& os);
    const Screen& display(std::ostream& os)const;
private:
    pos cursor = 0;
    pos height = 0, width = 0;
    std::string contents;
    void do_display(std::ostream& os)const {
        os << contents;
    }
};
inline Screen& Screen::set(char c) {
    contents[cursor] = c;
    return *this;
}
inline Screen& Screen::set(pos row, pos col,char c) {
    cursor = row * width + col;
    contents[row * width + col] = c;
    return *this;
}
inline Screen& Screen::move(pos row, pos col) {
    char newc = contents[cursor];
    contents[cursor] = contents[row * width + col];
    contents[row * width + col] = newc;
    return *this;
}
inline Screen& Screen::display(std::ostream& os)
{
    do_display(os);
    return *this;
}
inline const Screen& Screen::display(std::ostream& os) const
{
    do_display(os);
    return *this;
}
#endif // !SCREEN_H_

练习7.28

若函数返回类型变为Screen,则返回的是对象的副本,函数的操作只能添加于对象的副本上,对象的本身并没有改变。所以两次显示结果会不一样,myScreen不会输出#

练习7.29

#xxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxx

推测正确

练习7.30

1:当需要将一个对象作为整体引用而不是引用对象的一个成员时,使用this,则该函数返回对调用该函数的对象的引用。 

2:可以非常明确地指出访问的是调用该函数的对象的成员,且可以在成员函数中使用与数据成员同名的形参。  

缺点:不必要使用,代码多余。

练习7.31

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

练习7.32

class Screeen;

class Window_mgr
{
public:
    using ScreenIndex=std::vector<Screen>::size_type ;
    void clear(ScreenIndex);
private:
    std::vector<Screen> screens;
};
#include "Window_mgr.h"
class Screen {
public:
    friend void Window_mgr::clear(ScreenIndex);
    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) {}
    Screen set(char);
    Screen set(pos, pos,char);
    Screen move(pos, pos);
    Screen display(std::ostream& os);
    const Screen display(std::ostream& os)const;
private:
    pos cursor = 0;
    pos height = 0, width = 0;
    std::string contents;
    void do_display(std::ostream& os)const {
        os << contents;
    }
};

inline Screen Screen::set(char c) {
    contents[cursor] = c;
    return *this;
}
inline Screen Screen::set(pos row, pos col,char c) {
    cursor = row * width + col;
    contents[row * width + col] = c;
    return *this;
}
inline Screen Screen::move(pos row, pos col) {
    char newc = contents[cursor];
    contents[cursor] = contents[row * width + col];
    contents[row * width + col] = newc;
    return *this;
}
inline Screen Screen::display(std::ostream& os)
{
    do_display(os);
    return *this;
}
inline const Screen Screen::display(std::ostream& os) const
{
    do_display(os);
    return *this;
}
inline void Window_mgr::clear(ScreenIndex i)
{
    Screen& s = screens[i];
    s.contents = std::string(s.height * s.width, ' ');
}

练习7.33

C++ 成员声明中不允许限定名

Screen::pos Screen::size() const {
    return height * width;
}

练习7.34

Screen类的成员声明中出现pos且在pos的typedef之前的的一律报错。

练习7.35

typedef string Type;
Type initval();//外层作用域Money
class Exercise {
public:
    typedef double Type;//错误;不能重新定义Money
    Type setVal(Type);//Type类内匹配
    Type initVal();//Type类内匹配,在类内重新声明initVal函数
private:
    int val;
};
Type Exercise::setVal(Type parm) {//外层作用域Type
    val = parm + initVal();
    return val;
}

修改后

typedef string Type;
Type initval();
class Exercise {
public:
    typedef double Double_type;//修改类内的类型名与外部作用域类型名不相同
    Double_type setVal(Double_type);
    Double_type initVal();
private:
    int val;
};
Exercise::Double_type Exercise::setVal(Double_type parm) {
    val = parm + initVal();
    return val;
}

练习7.36

struct X
{
    X(int i, int j) :base(i), rem(base% j) {}//试图使用未初始化的base来初始化rem
    int rem, base;
};

更改成员出现顺序来更改成员的初始化顺序

struct X
{
    X(int i, int j) :base(i), rem(base% j) {}
    int base, rem;
};

练习7.37

Sales_data first_item(cin);//Sales_data(std::istream &is){read(is,*this) };与传入的值相关

int main(int argc, char* argv[])
{
    Sales_data next;//Sales_data(string s="") :bookNo(s){};cnt = 0, revenue = 0.0
    Sales_data last("9-999-99999-9");//Sales_data(string bn, unsigned cnt, double rev) :bookNo(bn), units_sold(cnt), revenue(cnt*rev) {};bookNo = "9-999-99999-9", cnt = 0, revenue = 0.0
}

练习7.38

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

练习7.39

不合法,这样的话接受string的构造函数和接受istream&的构造函数都可以接受无参函数,编译器无法判断要调用哪个函数

练习7.40

#ifndef TREE_H_
#define TREE_H_
#include <vector>
#include <string>
/*每棵树上都有很多分叉,每个分叉上都可能长着很多片叶子,
所以用Tree来描述树以及树上的枝干,用string来代表每一个枝干上的叶子
*/

class Tree
{
public:
    using leaf = std::string;
    Tree(std::vector<Tree> t, std::vector<leaf> l) : trees{ t }, leaves{ l } { }
    Tree(std::vector<Tree> t, leaf l) : trees{ t }, leaves{ l } { }
    Tree(Tree t, std::vector<leaf> l) : trees{ t }, leaves{ l } { }
    Tree(Tree t, leaf l) : trees{ t }, leaves{ l } { }
    void setTree(Tree t);
    void setTrees(std::vector<Tree> t);
    void setLeaf(leaf l);
    void setLeaves(std::vector<leaf> l);
    std::vector<Tree> getTrees();
    std::vector<leaf> getLeaves();
private:
    std::vector<Tree> trees;
    std::vector<leaf> leaves;
};
#endif // !TREE_H_

练习7.41

Sales_data(string bn, unsigned us = 0, double re = 0.0) :bookNo(bn), units_sold(us), revenue(re) { std::cout << "Sales_data(string bn, unsigned us = 0, double re = 0.0)" << std::endl;}
Sales_data() :Sales_data("") { std::cout << "Sales_data()" << std::endl; }
Sales_data(istream& is) :Sales_data() { read(is, *this); std::cout << "Sales_data(istream& is)" << std::endl; }
Sales_data first_item(cin);
Sales_data next;
Sales_data last("9-999-99999-9");
Sales_data(string bn, unsigned us = 0, double re = 0.0)
Sales_data()
1 1 1
Sales_data(istream& is)
Sales_data(string bn, unsigned us = 0, double re = 0.0)
Sales_data()
Sales_data(string bn, unsigned us = 0, double re = 0.0)

练习7.42

class Tree
{
public:
    using leaf = std::string;
    Tree(std::vector<Tree> t, std::vector<leaf> l) : trees(t), leaves(l) { }
    Tree(std::vector<Tree> t, leaf l) : Tree(t, std::vector<leaf>{l}) { }
    Tree(Tree t, std::vector<leaf> l) : Tree(std::vector<Tree>{t}, l) { }
    Tree(Tree t, leaf l) : Tree(std::vector<Tree>{t}, std::vector<leaf>{l}) { }
    void setTree(Tree t);
    void setTrees(std::vector<Tree> t);
    void setLeaf(leaf l);
    void setLeaves(std::vector<leaf> l);
    std::vector<Tree> getTrees();
    std::vector<leaf> getLeaves();
private:
    std::vector<Tree> trees;
    std::vector<leaf> leaves;
};

练习7.43

class NoDefault
{
public:
    NoDefault(int x) {};
private:

};
class C
{
public:
    C() :noDefault(0) {};
private:
    NoDefault noDefault;
};

练习7.44

不合法,因为NoDefault类没有默认构造函数,所以不能无参初始化

练习7.45

合法,因为C类有默认构造函数,而且能对成员类初始化

练习7.46

(a)不正确,如果类不提供构造函数,编译器会自动创建一个合成的默认构造函数

(b)不正确,当对象被默认初始化或值初始化都自动执行默认构造函数

(c)不正确,值初始化也需要类提供默认构造函数

(d)不正确,类没有定义任何构造函数的时候,编译器就会自动生成一个默认构造函数

练习7.47

应该。优点是可以避免隐式的类类型转换带来的风险,生成一个隐式转换后的类临时变量,完成操作后就消失了。缺点是用户使用时需要显式的创建临时对象,对用户提高了要求

练习7.48

string null_isbn("9-99-9999-9");
Sales_data item1(null_isbn);        // 用string类型的null_isbn直接初始化item1
Sales_data item2("9-99-9999-9");    // 用字符串字面值初始化item2.

练习7.49

Sales_data &combine(Sales_data); // 正常初始化,将s转化成Sales_data类型。
Sales_data &combine(Sales_data&);// 报错,string不能转化为Sales_data类型的引用。
Sales_data &combine(const Sales_data&) const;//报错,常量函数不允许对值做出改变。

练习7.50

explicit Person(istream& in) { read(in, *this); }//只有该构造函数是传入一个参数的

练习7.51

string存在const char*的隐式转换,而vector如果允许隐式转换则会提高理解难度

练习7.52

聚合类的原理(编译器遇到Plain OI' Data声明形式由于与C程序兼容,所以可以使用C程序形式进行初始化,从C++20开始, POD这一概念就被废止, 取而代之的是一系列更为精确的定义, 如TrivialType

要使用下面这种方式初始化则必然是聚合类,但是聚合类要求没有类内初始值,所以应该修改为

struct Sales_data
{
    std::string bookNo;
    unsigned units_sold;
    double revenue;
};

练习7.53

class Debug
{
public:
    constexpr Debug(bool h, bool i, bool o) :hw(h), io(i), other(o) {};
    constexpr Debug(bool b = true) :Debug(b, b, b) {};
    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

constexpr函数的返回值和所有形参的类型都必须得是字面值类型,而且函数体中必须有且只有一条return语句,可包含其他语句但不能在运行期起作用,例如;空语句等

所以可以被声明为constexpr

练习7.55

聚合类是字面值常量类,因为聚合类初始化必须使用字面值列表来进行初始化

练习7.56

和类本身相关但不是和类的对象保持联系的成员是类的静态成员,在成员声明前加上static关键字

优点:和类关联但不和类的对象关联,一旦修改所有对象都能使用新值

静态成员和普通成员的区别:不是在创建类的对象时被定义,而是在类的外部定义和初始化,因此静态数据成员可以是不完全类型。类的静态成员属于类本身,在类加载时就会分配内存,可以通过类名直接进行访问。普通成员属于类的对象,只有在类对象产生时才会分配内存。只能通过对象去访问。

练习7.57

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

static double rate = 6.5;//成员本身不是常量表达式
static const int vecSize = 20;
static std::vector<double> vec(vecSize);//vector对象不能在类内初始化
原文地址:https://www.cnblogs.com/GodZhuan/p/13910343.html