C ++ Primer(第五版)第七章练习答案

7.1.1 节练习

练习 7.1

使用 2.6.1 节练习定义的 Sales_data 类为 1.6 节(第 21 页)的交易处理程序编写一个新版本。

同练习 2.42

7.1.2 节练习

练习 7.2

曾在 2.6.2 节的练习(第 76 页)中编写了一个 Sales_data类,请向这个类添加 combine 和 isbn 成员。

#ifndef SALES_DATA_H_
#define SALES_DATA_H_
#include<string>
struct Sale_data
{
    /* data */
    std::string bookNo;// ISBN  
    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;
}
#endif

练习 7.3

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

#include<iostream>
#include<string>
#include"Sales_data.h"

int main()
{
    Sale_data total;// 保存下一条交易记录的变量
    double totalPrice;// 单价
    // 读入第一条交易记录,并确保有数据可以处理
    if (std::cin >> total.bookNo >> total.units_sold >> totalPrice)
    {
        total.revenue = total.units_sold * totalPrice;

        Sale_data trans;// 保存和的变量
        double transPrice;
        // 读入并处理剩余交易记录
        while (std::cin >> trans.bookNo >> trans.units_sold >> transPrice)
        {
            trans.revenue = trans.units_sold * transPrice;

            // 如果我们仍在处理相同的书
            if (total.bookNo == trans.bookNo)
            {
                // 更新总销售额
                total.combine(trans);
            }
            else 
            {              
		        // 打印前一本书的结果 
                std::cout << total.bookNo << " 的销售记录是:共售出 " << total.units_sold << " 本,总收入是 " << total.revenue << " 平均每本价格 "; 
                if (total.revenue!=0)
                {
                    std::cout << total.revenue / total.units_sold << std::endl;
                }
                else
                {
                    std::cout << "(no sales)" << std::endl;
                }
                total = trans;  // total 现在表示下一本书的销售额
            }
        }
        // 打印最后一本书的结果
        std::cout << total.bookNo << " 的销售记录是:共售出 " << total.units_sold << " 本,总收入是 " << total.revenue << " 平均每本价格 "; 
        if (total.revenue!=0)
        {
            std::cout << total.revenue / total.units_sold << std::endl;
        }
        else
        {
            std::cout << "(no sales)" << std::endl;
        }
    }else {
        // 没有输入! 警告读者
        std::cerr << "No data?!" << std::endl;
        return -1;  // 表示失败
    }
}

练习 7.4

编写一个名为 Person 的类,使其表示人员的姓名和住址。使用 string 对象存放这些元素,接下来的练习将不断充实这个类的其他特征。

#ifndef PERSON_H_
#define PERSON_H_

#include<string>

struct Person
{
    std::string name;
    std::string address;
};

#endif

练习 7.5

在你的 Person 类中提供一些操作使其能够返回姓名和地址。这些函数是否应该是 const 的呢?解释原因。

#ifndef PERSON_H_
#define PERSON_H_

#include<string>

struct Person
{
    std::string name;
    std::string address;

    std::string get_name() const { return name; }
    std::string get_address() const { return address; }
};


#endif

应该是 const,这两个成员函数只需读取成员对象,无需修改成员对象。

7.1.3 节练习

练习 7.6

对于函数 add、read、和 print,定义你自己的版本。

#ifndef SALES_DATA_H_
#define SALES_DATA_H_

#include<iostream>
#include<string>

using std::istream;
using std::ostream;
using std::string;

struct Sale_data
{
    /* data */
    string bookNo;// ISBN  
    string bookName;
    unsigned units_sold = 0; //销量
    double revenue = 0.0; //总收入

    string isbn() const { return bookNo; }
    Sale_data &combine(const Sale_data &);
    double avg_price() const;
};

Sale_data& Sale_data::combine(const Sale_data &rhs)
{
    units_sold += rhs.units_sold;
    revenue += rhs.revenue;

    return *this;
}

double Sale_data::avg_price()const
{
    if (units_sold)
    {
        return revenue / units_sold;
    }
    else
    {
        return 0;
    }
}

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

istream &read(istream &is, Sale_data &item)
{
    double price = 0;
    is >> item.bookNo >> item.units_sold >> price;
    item.revenue = price * item.units_sold;
    return is;
}

ostream &print(ostream &os, const Sale_data &item)
{
    os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
    return os;
}

#endif

练习 7.7

使用这些新函数重写 7.1.2 节(第 233 页)练习中的交易处理程序。

#include<iostream>
#include<string>
#include"Sales_data.h"

using std::cin;
using std::cout;
using std::endl;

int main()
{
    Sale_data total;// 保存下一条交易记录的变量
    // 读入第一条交易记录,并确保有数据可以处理
    if (read(cin, total))
    {
        Sale_data trans;// 保存和的变量
        // 读入并处理剩余交易记录
        while (read(cin, trans))
        {
            // 如果我们仍在处理相同的书
            if (total.isbn() == trans.isbn())
            {
                // 更新总销售额
                total.combine(trans);
            }
            else 
            {              
		        // 打印前一本书的结果
                print(cout, total);
                cout << endl;
                total = trans;  // total 现在表示下一本书的销售额
            }
        }
        // 打印最后一本书的结果
        print(cout, total);
        cout << endl;
    }else {
        // 没有输入! 警告读者
        std::cerr << "No data?!" << std::endl;
        return -1;  // 表示失败
    }
}

练习 7.8

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

因为 read 函数从给定流中将数据读到给定的对象里,会改变对象的内容;而 print 函数则负责将给定对象的内容打印到给定的流中,不会改变对象内容。

练习 7.9

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

#ifndef PERSON_H_
#define PERSON_H_

#include<iostream>
#include<string>

struct Person
{
    std::string name;
    std::string address;

    std::string get_name() const { return name; }
    std::string get_address() const { return address; }
};

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

std::ostream &print(std::ostream &os, const Person &item)
{
    return os << item.name << " " << item.address;
}
#endif

练习 7.10

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

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

连续读入 data1 和 data2,读入成功为真,失败为假。

7.1.4 节练习

练习 7.11

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

Sale_data.h

#ifndef SALES_DATA_H_
#define SALES_DATA_H_

#include<iostream>
#include<string>

using std::istream;
using std::ostream;
using std::string;

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(p * n){};
    Sales_data(istream &);

    string isbn() const { return bookNo; }
    Sales_data &combine(const Sales_data &);
    double avg_price() const;

    string bookNo;// ISBN  
    string bookName;
    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;
}

double Sales_data::avg_price()const
{
    if (units_sold)
    {
        return revenue / units_sold;
    }
    else
    {
        return 0;
    }
}

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

istream &read(istream &is, Sales_data &item)
{
    double price = 0;
    is >> item.bookNo >> item.units_sold >> price;
    item.revenue = price * item.units_sold;
    return is;
}

// 放在 read 函数之后,需要先声明 read 才能使用
Sales_data::Sales_data(istream &is) 
{ 
	read(is, *this); 
};

ostream &print(ostream &os, const Sales_data &item)
{
    os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
    return os;
}

#endif

7.11.cpp

#include<iostream>
#include"Sales_data.h"

using std::cin;
using std::cout;
using std::endl;

int main()
{
    Sales_data item1;
    print(cout, item1);
    cout << endl;

    Sales_data item2("002");
    print(cout, item2);
    cout << endl;

    Sales_data item3("003", 5, 12.2);
    print(cout, item3);
    cout << endl;

    Sales_data item4(cin);
    print(cout, item4);
    cout << endl;

    return 0;
}

练习 7.12

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

#ifndef SALES_DATA_H_
#define SALES_DATA_H_

#include<iostream>
#include<string>

using std::istream;
using std::ostream;
using std::string;

struct Sales_data;// read 函数的声明有使用到 Sales_data 类型的形参,所有要先声明
istream &read(istream &, Sales_data &);// 构造函数用到 read 函数,需要在使用前声明

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(p * n){};
    Sales_data(istream &is) { read(is, *this); };

    string isbn() const { return bookNo; }
    Sales_data &combine(const Sales_data &);
    double avg_price() const;

    string bookNo;// ISBN  
    string bookName;
    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;
}

double Sales_data::avg_price()const
{
    if (units_sold)
    {
        return revenue / units_sold;
    }
    else
    {
        return 0;
    }
}

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

istream &read(istream &is, Sales_data &item)
{
    double price = 0;
    is >> item.bookNo >> item.units_sold >> price;
    item.revenue = price * item.units_sold;
    return is;
}

ostream &print(ostream &os, const Sales_data &item)
{
    os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
    return os;
}

#endif

练习 7.13

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

#include<iostream>
#include"Sales_data.h"

using std::cerr;
using std::cin;
using std::cout;
using std::endl;

int main()
{
    Sales_data total(cin);
    if (cin)
    {
        while (cin)
        {
            Sales_data trans(cin);
            if (total.isbn()==trans.isbn())
            {
                total.combine(trans);
            }
            else
            {
                print(cout, total) << endl;
                total = trans;
            }
        }
    }else
    {
        cerr << "No data?!" << endl;
    }
    
    return 0;
}

练习 7.14

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

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

练习 7.15

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

#ifndef PERSON_H_
#define PERSON_H_

#include<iostream>
#include<string>

struct Person;
std::istream &read(std::istream &, Person &);

struct Person
{
    Person() = default;
    Person(std::string &n, std::string &ad) : name(n), address(ad){};
    Person(std::istream &is) { read(is, *this); };

    std::string name;
    std::string address;

    std::string get_name() const { return name; }
    std::string get_address() const { return address; }
};

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

std::ostream &print(std::ostream &os, const Person &item)
{
    return os << item.name << " " << item.address;
}
#endif

7.2 节练习

练习 7.16

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

一个类可以包含0个或多个访问说明符,而且对于某个访问说明符能出现多少次也没有严格的限定。

public:成员在整个程序内可被访问,public 成员定义类的接口;
private:成员可以被类的成员函数访问,但是不能被使用该类的代码访问,private 部分封装了(即隐藏了)类的实现细节。

练习 7.17

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

区别是两者的默认访问权限不同。

class 的默认访问权限是 private
struct 的默认访问权限是 public

练习 7.18

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

封装是实现与接口的分离。它隐藏了类型的实现细节。

封装的优点,一是确保用户代码不会无意间破坏封装对象的状态。二是被封装的类的具体实现细节可以随时改变,而无需调整用户级别的代码。

练习 7.19

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

struct Person
{
public:
    Person() = default;
    Person(std::string &n, std::string &ad) : name(n), address(ad){};
    Person(std::istream &is) { read(is, *this); };
    std::string get_name() const { return name; }
    std::string get_address() const { return address; }
    
private:
    std::string name;
    std::string address;
};

将数据成员声明成 private,避免数据被外部修改。将成员函数声明成 public,供外部调用

7.2.1 节练习

练习 7.20

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

友元在有其他类或函数访问它的非公有成员时,可以令其获得访问的权限。但可能破坏类的封装。

练习 7.21

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

#ifndef SALES_DATA_H_
#define SALES_DATA_H_

#include<iostream>
#include<string>

using std::istream;
using std::ostream;
using std::string;

struct Sales_data;// read 函数的声明有使用到 Sales_data 类型的形参,所有要先声明
istream &read(istream &, Sales_data &);// 构造函数用到 read 函数,需要在使用前声明
ostream &print(ostream &, const Sales_data &);

struct Sales_data
{
    // 友元函数声明前,在类前再一次声明
    friend istream &read(istream &, Sales_data &);
    friend ostream &print(ostream &, const Sales_data &);
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(p * n){};
    Sales_data(istream &is) { read(is, *this); };

    string isbn() const { return bookNo; }
    Sales_data &combine(const Sales_data &);
    double avg_price() const;
private:
    string bookNo;// ISBN  
    string bookName;
    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;
}

double Sales_data::avg_price()const
{
    if (units_sold)
    {
        return revenue / units_sold;
    }
    else
    {
        return 0;
    }
}

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

istream &read(istream &is, Sales_data &item)
{
    double price = 0;
    is >> item.bookNo >> item.units_sold >> price;
    item.revenue = price * item.units_sold;
    return is;
}

ostream &print(ostream &os, const Sales_data &item)
{
    os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
    return os;
}

#endif

练习 7.22

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

#ifndef PERSON_H_
#define PERSON_H_

#include<iostream>
#include<string>

struct Person;
std::istream &read(std::istream &, Person &);
std::ostream &print(std::ostream &, const Person &);

struct Person
{
    friend std::istream &read(std::istream &, Person &);
    friend std::ostream &print(std::ostream &, const Person &);

public:
    Person() = default;
    Person(std::string &n, std::string &ad) : name(n), address(ad){};
    Person(std::istream &is) { read(is, *this); };
    std::string get_name() const { return name; }
    std::string get_address() const { return address; }
    
private:
    std::string name;
    std::string address;
};

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

std::ostream &print(std::ostream &os, const Person &item)
{
    return os << item.name << " " << item.address;
}
#endif

7.3.1 节练习

练习 7.23

编写你自己的 Screen 类型。

#ifndef SCREEN_H_
#define SCREEN_h_

#include<string>

class Screen
{
public:
    using pos = std::string::size_type;
    
    char get() const { return contents[cursor]; }
    inline char get(pos ht, pos wd) const;

private:
    pos cursor = 0;
    pos height = 0, width = 0;
    std::string contents;
};

char Screen::get(pos r,pos c)const
{
    pos row = r * width;
    return contents[row + c];
}

#endif

练习 7.24

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

#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){}

    char get() const { return contents[cursor]; }
    inline char get(pos ht, pos wd) const;

private:
    pos cursor = 0;
    pos height = 0, width = 0;
    std::string contents;
};

char Screen::get(pos r,pos c)const
{
    pos row = r * width;
    return contents[row + c];
}

#endif

练习 7.25

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

Screen 类中的四个成员对象都是内置类型和 string 类型,因此能够安全地依赖于拷贝和赋值操作的默认版本。

练习 7.26

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

inline double Sales_data::avg_price() const
{
    if (units_sold)
    {
        return revenue / units_sold;
    }
    else
    {
        return 0.0;
    }
}

7.3.2 节练习

练习 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<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]; }
    inline char get(pos ht, pos wd) const;

    Screen &move(pos r, pos c);

    Screen &set(char);
    Screen &set(pos, pos, char);

    Screen &display(std::ostream &os) 
    {   
        do_display(os);
        return *this;
    }
    const Screen &display(std::ostream &os) const
    {
        do_display(os);
        return *this;
    }

private:
    pos cursor = 0;
    pos height = 0, width = 0;
    std::string contents;

    void do_display(std::ostream &os) const { os << contents; };
};

char Screen::get(pos r,pos c)const
{
    pos row = r * width;
    return contents[row + c];
}

inline Screen &Screen::move(pos r, pos c)
{
    pos row = r * width;
    cursor = row + c;
    return *this;
}

inline Screen &Screen::set(char c)
{
    contents[cursor] = c;
    return *this;
}

inline Screen &Screen::set(pos r, pos col, char ch)
{
    contents[r * width + col] = ch;
    return *this;
}

#endif

7.27.cpp

#include<iostream>
#include"Screen.h"

using std::cout;

int main()
{
    Screen myScreen(5, 5, 'X');
    myScreen.move(4, 0).set('#').display(cout);
    cout << "\n";
    myScreen.display(cout);
    cout<< "\n";
}

结果

XXXXXXXXXXXXXXXXXXXX#XXXX
XXXXXXXXXXXXXXXXXXXX#XXXX

练习 7.28

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

由于改成 Screen 类型,则 move、set 和 display 都是返回 myScreen 的临时副本,所有的操作改变的都是临时副本,所以第一次的 display() 的结果是临时副本,已经被改变;而第二次的 display() 结果重新拷贝 myScreen 的副本,没改变。

练习 7.29

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

XXXXXXXXXXXXXXXXXXXX#XXXX
XXXXXXXXXXXXXXXXXXXXXXXXX

练习 7.30

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

优点:
更明确,减少误读的可能性;
可以使用名称与成员名相同的形参。

缺点:
冗余代码增加。

7.3.3 节练习

练习 7.31

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

#ifndef XY_H_
#define XY_H_

class Y;// 先声明 Y 类型 才能在 X 类中使用

class X
{
    Y *y;
};


class Y
{
    X x;
};

#endif

7.3.4 节练习

练习 7.32

定义你自己的 Screen 和 Window_mgr,其中 clear 是 Window_mgr 的成员,是 Screen 的友元。

#ifndef SCREEN_H_
#define SCREEN_h_

#include<iostream>
#include<string>
#include<vector>

class Screen;

class Window_mgr
{
public:
    using ScreenIndex = std::vector<Screen>::size_type;
    void clear(ScreenIndex);
private:
    std::vector<Screen> screens{Screen(24, 80, ' ')};
};

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]; }
    inline char get(pos ht, pos wd) const;

    Screen &move(pos r, pos c);

    Screen &set(char);
    Screen &set(pos, pos, char);

    Screen &display(std::ostream &os) 
    {   
        do_display(os);
        return *this;
    }
    const Screen &display(std::ostream &os) const
    {
        do_display(os);
        return *this;
    }

private:
    pos cursor = 0;
    pos height = 0, width = 0;
    std::string contents;

    void do_display(std::ostream &os) const { os << contents; };
};

char Screen::get(pos r,pos c)const
{
    pos row = r * width;
    return contents[row + c];
}

inline Screen &Screen::move(pos r, pos c)
{
    pos row = r * width;
    cursor = row + c;
    return *this;
}

inline Screen &Screen::set(char c)
{
    contents[cursor] = c;
    return *this;
}

inline Screen &Screen::set(pos r, pos col, char ch)
{
    contents[r * width + col] = ch;
    return *this;
}

#endif

7.4 节练习

练习 7.33

如果我们给 Screen 添加一个如下所示的 size 成员将发生什么情况?如果出现了问题,请尝试修改它。

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

返回类型错误。由于返回类型使用的名字都是在作用域之外,所以即使之后的的函数名前加了类名,返回类型也必须加上类名。改为:

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

7.4.1 节练习

练习 7.34

如果我们把第 256 页 Screen 类的 pos 的 typedef 放在类的最后一行会发生什么情况?

dummy_fcn(pos height) 中的 pos 未声明,将会报错。

练习 7.35

解释下面代码的含义,说明其中的 Type 和 initVal 分别使用了哪个定义。如果代码存在错误,尝试修改它。

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;
}

修改

typedef string Type;
Type initVal(); // 全局函数声明,Type 使用 string 定义
class Exercise {
public:
    typedef double Type;
    Type setVal(Type);// 成员函数声明,返回类型和形参类型都使用 double 定义
    Type initVal(); // 成员函数声明,隐藏了同名的全局函数,返回类型 double
private:
    int val;
};
Exercise::Type Exercise::setVal(Type parm) { // 返回类型改成 double定 义,形参是 double 定义
    val = parm + initVal();// 成员函数 initVal
    return val;
}

7.5.1 节练习

练习 7.36

下面的初始值是错误的,请找出问题所在并尝试修改它。

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

成员初始化与它们的顺序一致,代码中 rem 比 base 先初始化,而构造函数中初始化 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(s)
    // bookNo="", units_sold=0, revenue=0.0
    Sales_data last("9-999-99999-9");
    // 使用默认构造函数,Sales_data(std::string s = ""): bookNo(s)
    // bookNo="9-999-99999-9", units_sold=0, revenue=0.0
}

练习 7.38

有些情况下我们希望提供 cin 作为接受 istream& 参数的构造函数的默认实参,请声明这样的构造函数。

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

练习 7.39

如果接受 string 的构造函数和接受 istream& 的构造函数都使用默认实参,这种行为合法吗?如果不,为什么?

不合法。此时使用构造函数且不输入参数,则两个构造函数都可以为其进行默认构造,将无法选择使用哪个函数,引起二义性。

练习 7.40

从下面的抽象概念中选择一个(或者你自己指定一个),思考这样的类需要哪些数据成员, 提供一组合理的构造函数并阐明这样做的原因。

(a) Book
(b) Data
(c) Employee
(d) Vehicle
(e) Object
(f) Tree
#ifndef TREE_H_
#define TREE_H_

#include<string>

class Tree
{
private:
    std::string name;
    int height;
    int age;

public:
    Tree() = default;
    Tree(std::string &n, int h, int a) : name(n), height(h), age(a){}
};

#endif

7.5.2 节练习

练习 7.41

使用委托构造函数重新编写你的 Sales_data 类,给每个构造函数体添加一条语句,令其一旦执行就打印一条信息。用各种可能的方式分别创建 Sales_data 对象,认真研究每次输出的信息直到你确实理解了委托构造函数的执行顺序。

struct Sales_data
{
    // 友元函数声明前,在类前再一次声明
    friend istream &read(istream &, Sales_data &);
    friend ostream &print(ostream &, const Sales_data &);
public:
    // 委托构造函数
    Sales_data(const string &s, unsigned n, double p) : bookNo(s), units_sold(n), revenue(p * n) { cout << "委托构造函数 Sales_data(const string &s, unsigned n, double p)" << endl; };
    // 其他都设置成受委托构造函数
    Sales_data() : Sales_data("", 0, 0.0) { cout << "构造函数 Sales_data() : Sales_data("", 0, 0.0)" << endl; }
    Sales_data(const string &s) : Sales_data(s, 0, 0.0) { cout << "构造函数 Sales_data(const string &s) : Sales_data(s, 0, 0.0)" << endl; }
    Sales_data(istream &is) :Sales_data()
    {
        read(is, *this); 
        cout << "构造函数 Sales_data(istream &is) :Sales_data()" << endl;
    }
// 其他不变
};

创建对象

#include<iostream>
#include"Sales_data.h"
#include<string>

using std::cin;

int main()
{
    Sales_data s1;
    Sales_data s2("001");
    Sales_data s3(cin);
    return 0;
}

输出

委托构造函数 Sales_data(const string &s, unsigned n, double p)
构造函数 Sales_data() : Sales_data("", 0, 0.0)
委托构造函数 Sales_data(const string &s, unsigned n, double p)
构造函数 Sales_data(const string &s) : Sales_data(s, 0, 0.0)
委托构造函数 Sales_data(const string &s, unsigned n, double p)
构造函数 Sales_data() : Sales_data("", 0, 0.0)
002
^Z
构造函数 Sales_data(istream &is) :Sales_data()

练习 7.42

对于你在练习 7.40 (参见 7.5.1 节,第 261 页)中编写的类,确定哪些构造函数可以使用委托。如果可以的话,编写委托构造函数。如果不可以,从抽象概念列表中重新选择一个你认为可以使用委托构造函数的,为挑选出的这个概念编写类定义。

#ifndef TREE_H_
#define TREE_H_

#include<string>
#include<iostream>

class Tree
{
private:
    std::string name;
    int height;
    int age;

public:
    Tree(const std::string &n, int h, int a) : name(n), height(h), age(a){}

    Tree() : Tree("", 0, 0){}
    Tree(const std::string &n) : Tree(n, 0, 0){}
    Tree(std::istream &is) : Tree() { is >> name >> height >> age; }
};

#endif

7.5.3 节练习

练习 7.43

假定有一个名为 NoDefault 的类,它有一个接受 int 的构造函数,但是没有默认构造函数。定义类 C, C 有一个 NoDefault 类型的成员,定义 C 的默认构造函数。

#include<iostream>

struct Nodefault
{
    Nodefault(int){}
};

class C
{
private:
    Nodefault def;

public:
    C() : def(0){}
};

int main()
{
    C c();
    return 0;
}

练习 7.44

下面这条声明合法吗?如果不,为什么?

vector<NoDefault> vec(10);

不合法。NoDefault 类没有默认构造函数。

练习 7.45

如果在上一个练习中定义的 vector 的元素类型是 C,则声明合法吗?为什么?

合法。因为 C 类有默认构造函数。

练习 7.46

下面哪些论断是不正确的?为什么?
( a ) 一个类必须至少提供一个构造函数。
( b )默认构造函数是参数列表为空的构造函数。
( c ) 如果对于类来说不存在有意义的默认值,则类不应该提供默认构造函数。
( d )如果类没有定义默认构造函数,则编译器将为其生成一个并把每个数据成员初始化成相应类型的默认值。

(a)错误;没有构造函数时,有时可以生成默认构造函数;

(b)错误;默认构造函数也可以有参数,为对应的成员提供默认初始化;

(c)错误;一般情况下,类需要一个默认构造函数来初始化其成员;

(d)错误;只要类内有了构造函数,编译器就不会再为其生成构造函数。

7.5.4 节练习

练习 7.47

说明接受一个 string 参数的 Sales_data 构造函数是否应该是 explicit 的,并解释这样做的优缺点。

应该是 explicit 的,这样可以防止编译器自动的把一个 string 对象转换成 Sales_data 对象。

优点:
防止隐式转换的产生;
缺点:
只需一步类型的转换。

练习 7.48

假定 Sales_data 的构造函数不是 explicit 的,则下边定义将执行什么样的操作?如果 Sales_data 的构造函数是 explicit 的,又会发生什么呢?

string null_isbn("9-999-9999-9");
Sales_data item1(null_isbn);
Sales_data item2("9-999-99999-9");

都不会有问题,都显示构造了Sales_data对象。

练习 7.49

对于 combine 函数的三种不同声明,当我们调用 i.combine(s) 时分别发生什么情况?其中 i 是一个 Sales_data,而 s 是一个 string 对象.

(a)Sales_data & combine(Sales_data);
(b)Sales_data & combine(Sales_data &);
(c)Sales_data & combine(const Sales_data &) const;

(a)正确;s 隐式调用了 Sales_data 的构造函数,生成临时对象并传递给 combine 的形参。

(b)错误。因为 combine 成员函数的形参是非常量引用,但是 s 自动创建的 Sales_data 临时对象无法传递给 combine 所需的非常量引用。(PS:隐式转换生成的无名的临时对象是 const 的)。

==(c)错误。第二个 const 把 combine 函数设置成为常量成员函数,不能改变成员数据的值,则 i.combine(s) 不能改变 i,编译不通过。 ==

练习 7.50

确定在你的 Person 类中是否有一些构造函数应该是 explicit 的。

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

练习 7.51

vector 将其单参数的构造函数定义成 explicit 的,而 string 则不是,你觉得原因何在?

void func(vector<int> v)

如果 vector 单参数构造函数不是 explicit 的,那么对于上述函数来说,可以直接以这样的形式进行调用 fun(5),这种调用容易引起歧义,无法得知实参 5 指的是 vector 的元素个数还是只有一个值为 5 的元素。

void func(std::string); 
func("hello"); 

而 string 类型不是一个容器,不存在这样的歧义问题。

7.5.5 节练习

练习 7.52

使用 2.6.1节(第 64 页)的 Sales_data 类,解释下面的初始化过程。如果存在问题,尝试修改它。

Sales_data item = {"987-0590353403", 25, 15.99};

使用花括号括起来的成员初始值列表,初始化聚合类的数据成员。

将 Sales_data 的 bookNo 成员初始化为 “978-0590353403”,将 units_sold 初始化为25,将 revenue 初始化为 15.99。

7.5.6 节练习

练习 7.53

定义你自己的 Debug。

#ifndef DEBUG_H_
#define DEBUG_H_

class Debug
{
private:
    bool hw;
    bool io;
    bool other;

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; }
};

#endif

练习 7.54

Debug 中以 set_ 开头的成员应该被声明成 constexpr 吗?如果不,为什么?

不能。constexpr 函数只能有一条返回语句。

练习 7.55

7.5.5节(第 266 页)的 Data 类是字面值常量类吗?请解释原因。

不是。s 的数据类型 string 不是字面值类型。

7.6 节练习

练习 7.56

什么是类的静态成员?它有何优点?静态成员与普通成员有何区别?

静态成员是指声明语句之前带有关键字 static 的类成员,静态成员不是任意单独对象的组成部分,而是由该类的全体对象所共享。

静态成员的优点包括:
(1)作用域位于类的范围之内,避免与其他类的成员或者全局作用域的名字冲突;
(2)可以是私有成员,而全局对象不可以;
(3)通过阅读程序可以非常容易地看出静态成员与特定类关联,使得程序的含义清晰明了。
(4)可以作为公共数据,每个对象不需要存储公共数据,如果数据被改变,则每个对象都可以使用新值。

静态成员与普通成员的区别:
(a)静态成员是某个具体对象的组成部分;而静态成员不从属于任何具体的对象,它由该类的所有对象共享。
(b)静态成员可以作为默认参数,而普通成员不能作为默认参数。

练习 7.57

编写你自己的 Account 类。

#ifndef ACCOUNT_H_
#define ACCOUNT_H_

#include<string>

class Account
{
private:
    std::string owner;
    double amount;
    static double interestRate;
    static double initRate();

public:
    void calcuate() { amount += amount * interestRate; }
    static double rate() { return interestRate; }
    static void rate(double newRate) { interestRate = newRate; }
};

double Account::interestRate = initRate();

#endif

练习 7.58

下面的静态数据成员的声明和定义有错误吗?请解释原因。

//example.h
class Example {
public:
    static double rate = 6.5;
    static const int vecSize = 20;
    static vector<double> vec(vecSize);
};
//example.c
#include "example.h"
double Example::rate;
vector<double> Example::vec;

rate 初始化错误,在类内初始化的静态成员必须是字面值常量的 constexp 静态成员,如 vecSize 就可以类内初始化。

同理,vector 也不能类内初始化,它是非字面值类型。

vecSize 即使一个常量静态数据成员在类内部被初始化了,通常情况也应该在类外部定义一下。

修改:

//example.h
class Example {
public:
    static double rate;
    static const int vecSize = 20;
    static vector<double> vec;
};
const int Example::vecSize;
//example.c
#include "example.h"
double Example::rate = 6.5;
vector<double> Example::vec(Exaple::vecSize);
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值