《C++ Primer》第7章 7.3节习题答案

本文详细讲解了如何根据《C++ Primer》7.3节内容,设计Screen类,包括数据成员如屏幕尺寸、内容和光标位置,以及构造函数、移动、设置和显示等函数。此外,探讨了友元函数clear在Window_mgr和Screen之间的运用,以及类间关系的实现方式。
摘要由CSDN通过智能技术生成

《C++ Primer》第7章 类

7.3节 类的其他特性

练习7.23:编写你自已的Screen类。
【出题思路】
思考Screen类应该包含哪些数据成员和函数成员,设置适当的访问权限。
【解答】
对于Screen类来说,必不可少的数据成员有:屏幕的宽度和高度、屏幕的内容以及光标的当前位置,这与书中的示例是一致的。因此,仅包含数据成员的Screen类是:

class Screen
{
private:
    unsigned height = 0;        //屏幕的高
    unsigned width = 0;         //屏幕的宽
    unsigned cursor = 0;        //光标的当前位置
    unsigned contents;          //屏幕的内容
};

练习7.24:给你的Screen类添加三个构造函数:一个默认构造函数;另一个构造函数接受宽和高的值,然后将contents初始化成给定数量的空白;第三个构造函数接受宽和高的值以及一个字符,该字符作为初始化之后屏幕的内容。
【出题思路】
同一个类可以包含多个构造函数,构造函数的定义可以在类的内部也可以在类的外部。
【解答】
使用构造函数的列表初始值执行初始化操作,添加构造函数之后的Screen类是:

#include <iostream>
#include <string>

using namespace std;

class Screen
{
private:
    unsigned height = 0;
    unsigned width = 0;
    unsigned cursor = 0;
    string contents;

public:
    Screen() = default;
    Screen(unsigned ht, unsigned wd)
            :height(ht), width(wd), contents(ht * wd, ' ')
    {

    }

    Screen(unsigned ht, unsigned wd, char c)
            :height(ht), width(wd), contents(ht * wd, c)
    {

    }
    void print()
    {
        cout << "width = " << width << "\theight = " << height << "\tcursor = " << cursor << "\tcontents = " << contents << endl;
    }
};

int main()
{
    cout << "打印屏幕信息:" << endl;
    Screen screen1;
    screen1.print();

    Screen screen2(50, 10);
    screen2.print();

    Screen screen3(5, 2, 'A');  // 5 * 2 个'A'
    screen3.print();

    return 0;
}

运行结果:

练习7.25:Screen能安全地依赖于拷贝和赋值操作的默认版本吗?如果能,为什么?如果不能,为什么?
【出题思路】
含有指针数据成员的类一般不宜使用默认的拷贝和赋值操作,如果类的数据成员都是内置类型的,则不受干拢。
【解答】
Screen的4个数据成员都是内置类型(string类定义了拷贝和赋值运算符),因此直接使用类对象执行拷贝和赋值操作是可以的。

练习7.26:将Sales_data::avg_price定义成内联函数。
【出题思路】
要想把类的成员函数定义成内联函数,有几种不同的途径。第一种是直接把函数定义放在类的内部,第二种是把函数定义放在类的外部,并且在定义之前显式地指定inline。
【解答】
隐式内联,把avg_price函数的定义放在类的内部:

//隐式内联,把avg_price函数的定义放在类的内部
class Sales_data
{
public:
    double avg_privce() const
    {
        if(units_sold)
        {
            return revenue / units_sold;
        }
        else
        {
            return 0;
        }
    }
};


//显式内联,把avg_price函数的定义放在类的外部,并且指定inline
class Sales_data
{
    double avg_price() const;
};


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

 练习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";

【出题思路】
添加3个成员函数,注意函数的返回值类型应该是引用类型,在成员函数内部可以直接使用类的数据成员。
【解答】
添加move、set和display函数之后,新的Screen类是:

#include <iostream>
#include <string>

using namespace std;

class Screen
{
public:
    Screen() = default;
    Screen(unsigned width, unsigned height):
        m_width(width), m_height(height), contents(width * height, ' ')
    {

    }
    Screen(unsigned width, unsigned height, char c):
        m_width(width), m_height(height), contents(width * height, c)
    {

    }

    Screen& move(unsigned int row, unsigned int col)
    {
        m_cursor = row * m_width + col;
        return *this;
    }

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

    Screen& set(unsigned int row, unsigned int col, char c)
    {
        contents[row * m_width + col] = c;
        return *this;
    }

    Screen& display(std::ostream &os)
    {
        cout << "Screen& display======================" << endl;
        do_display(os);
        return *this;
    }

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

private:
    unsigned int m_width = 0;
    unsigned int m_height = 0;
    unsigned int m_cursor = 0;
    string contents;
};

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

    return 0;
}

运行结果:

 

练习7.28:如果move、set和display函数的返回类型不是Screen&而是Screen,则在上一个练习中将会发生什么情况?
【出题思路】
函数的返回值如果是引用,则表明函数返回的是对象本身;函数的返回值如果不是引用,则表时函数返回的是对象的副本。
【解答】
返回引用的函数是左值的,意味着这些函数返回的是对象的本身而非对象的副本。如果我们把一系列这样的操作连接在一起的话,所有这些操作将在同一个对象上执行。在上一个练习中,move,set和display函数的返回类型都是Screen&表示我们首先移动光标到(4,0)位置,然后将该位置的字符修改为'#',最后输出myScreen的内容。相反,如果我们把move,set和display函数返回类型改成Screen,则上述函数各自只返回一个临时副本,不会改变myScreen的值。

 

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

【出题思路】

在编程环境中验证修改前后的程序差别,注意对比运行结果的变化并思考原因。

【解答】

修改move、set和display函数的返回类型后运行程序,得到的结果是:

#include <iostream>
#include <string>

using namespace std;

class Screen
{
public:
    Screen() = default;
    Screen(unsigned width, unsigned height):
        m_width(width), m_height(height), contents(width * height, ' ')
    {

    }
    Screen(unsigned width, unsigned height, char c):
        m_width(width), m_height(height), contents(width * height, c)
    {

    }

    Screen move(unsigned int row, unsigned int col)
    {
        m_cursor = row * m_width + col;
        return *this;
    }

    Screen set(char c)
    {
        contents[m_cursor] = c;
        return *this;
    }

    Screen set(unsigned int row, unsigned int col, char c)
    {
        contents[row * m_width + col] = c;
        return *this;
    }

    Screen display(std::ostream &os)
    {
        cout << "Screen display======================" << endl;
        do_display(os);
        return *this;
    }

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

private:
    unsigned int m_width = 0;
    unsigned int m_height = 0;
    unsigned int m_cursor = 0;
    string contents;
};

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

    return 0;
}

运行结果:

 练习7.30:通过this指针使用成员的做法虽然合法,但是有点多余。讨论显示地使用指针访问成员的优缺点。
【出题思路】
对比使用this指针访问成员的利弊。
【解答】
通过this指针访问成员的优点是,可以非常明确地指出访问的是对象的成员,并且可以在成员函数中使用与数据成员同名的形参;缺点是显得多余,代码不够简洁。

练习7.31:定义一对类X和Y,其中X包含一个指向Y的指针,而Y包含一个类型为X的对象。
【出题思路】
理解类的声明和定义。声明的作用是告知程序类的名字合法可用;定义的作用是规定类的细节。
【解答】
满足题意的程序如下所示:

class X;
class Y
{
    X* x;//可以定义不完全类型的指针,但是无法创建不完全类型的对象。
};

class X
{
    Y y;
};

类X的声明称为前向声明,它向程序中引入了名字X并且指明X是一种类类型。对于类型X来说,此时我们已知它是一个类类型,但是不清楚它到底包含哪些成员,所以它是一个不完全类型。我们可以定义指向不完全类型的指针,但是无法创建不完全类型的对象。

如果试图写成下面的形式,将引发编译器错误。

class Y;
class X
{
    Y y;  //错误,创建不完全类型Y的对象
};

class Y
{
    X* x;
};

练习7.32:定义你自己的Screen和Window_mgr,其中clear是Window_mgr的成员,是Screen的友元。
【出题思路】
类可以把其他类定义成友元,也可以把其他类的成员函数定义成友元。当把成员函数定义成友元时,要特别注意程序的组织结构。
【解答】
要想让clear函数作为Screen的友元,只需要在Screen类中做出友元声明即可。本题的真正关键之处是程序的组织结构,我们必须首先定义Window_mgr类,其中声明clear函数,但是不能定义它;接下来定义Screen类,并且在其中指明clear函数是其友元;最后定义clear函数。满足题意的程序如下所示:

#include <iostream>
#include <string>

using namespace std;

class Window_mgr
{
public:
    void clear();
};

class Screen
{
    friend void Window_mgr::clear();        //不定义友元,编译通不过
private:
    unsigned height = 0, width = 0;
    unsigned cursor = 0;
    string contents;

public:
    Screen() = default;
    Screen(unsigned ht, unsigned wd, char c)
        :height(ht), width(wd), contents(ht * wd, c)
    {

    }
};

void Window_mgr::clear()
{
    Screen myScreen(10, 20, 'X');
    cout << "请理之前myScreen的内容是:" << endl;
    cout << myScreen.contents << endl;
    myScreen.contents = "";
    cout << "请理之后myScreen的内容是:" << endl;
    cout << myScreen.contents << endl;
}

int main()
{
    Window_mgr w;
    w.clear();

    return 0;
}

运行结果:

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值