天天看點

C++Primer第五版 習題答案 第七章 類(Classes)

練習7.1

使用2.6.1節定義的Sales_data類為1.6節的交易處理程式編寫一個新版本。

見習題2.41。

練習7.2

曾在2.6.2節的練習中編寫了一個Sales_data類,請向這個類添加combine函數和isbn成員。
#ifndef SALES_DATA_H_
#define SALES_DATA_H_

#include <string>

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

    std::string isbn() const {return bookNo;}
    Sales_data& combine(const Sales_data&);
};

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

	return *this;
}

#endif
           

練習7.3

修改7.1.1節的交易處理程式,令其使用這些成員。
#include <iostream>
#include <string>
#include "Sales_data.h"

int main()
{
    Sales_data total;
    double totalPrice;

    if (std::cin >> total.bookNo >> total.units_sold >> totalPrice)
    {
        total.revenue = total.units_sold * totalPrice;

        Sales_data trans;
        double transPrice;
        while (std::cin >> trans.bookNo >> trans.units_sold >> transPrice)
        {
            trans.revenue = trans.units_sold * transPrice;

            if (total.isbn() == trans.isbn())
            {
                total.combine(trans);
            }
            else
            {
                std::cout << total.bookNo << " " << total.units_sold << " " << total.revenue << " ";
                if (total.units_sold != 0)
                    std::cout << total.revenue / total.units_sold << std::endl;
                else
                    std::cout << "(no sales)" << std::endl;

                total = trans;
            }
        }

        std::cout << total.bookNo << " " << total.units_sold << " " << total.revenue << " ";
        if (total.units_sold != 0)
            std::cout << total.revenue / total.units_sold << std::endl;
        else
            std::cout << "(no sales)" << std::endl;

        return 0;
    }
    else
    {
        std::cerr << "No data?!" << std::endl;
        return -1;  // indicate failure
    }
}
           

練習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的呢?解釋原因。

應該是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
           

練習7.6

對于函數add、read和print,定義你自己的版本。
#ifndef SALES_DATA_H_
#define SALES_DATA_H_

#include <string>

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

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

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

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 << " " << item.avg_price();

	return os;
}

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

	return sum;
}

#endif
           

練習7.7

使用這些新函數重寫7.1.2節練習中的程式。
#include <iostream>
#include <string>
#include "Sales_data_ex06.h"

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::cout << std::endl;
                total = trans;
            }
        }
        print(std::cout, total);
        std::cout << std::endl;

        return 0;
    }
    else
    {
        std::cerr << "No data?!" << std::endl;
        return -1;  // indicate failure
    }
}
           

練習7.8

為什麼read函數将其Sales_data參數定義成普通的引用,而print函數将其參數定義成常量引用?

因為read函數需要改變成員對象;而print隻需讀取成員對象。

練習7.9

對于7.1.2節練習中代碼,添加讀取和列印Person對象的操作。
#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;}
};

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語句中,條件部分的作用是什麼?

讀入data1和data2,并判斷傳回是否為真。

練習7.11

在你的Sales_data類中添加構造函數,然後編寫一段程式令其用到每個構造函數。

Sales_data_ex11.h

#ifndef SALES_DATA_H_
#define SALES_DATA_H_

#include <string>

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(p*n){}
	Sales_data(std::istream &);
	std::string isbn() const {return bookNo;}
    Sales_data& combine(const Sales_data&);
    double avg_price() const;

    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
};

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

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 << " " << item.avg_price();

	return os;
}

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

	return sum;
}

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

#endif
           

ex11.cpp

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

int main()
{
	Sales_data sales_data1;
	print(std::cout, sales_data1) << std::endl;

	Sales_data sales_data2("1-01");
	print(std::cout, sales_data2) << std::endl;

	Sales_data sales_data3("1-01", 1, 100);
	print(std::cout, sales_data3) << std::endl;

	Sales_data sales_data4(std::cin);
	print(std::cout, sales_data4) << std::endl;

	// Sales_data sales_data5();
	// print(std::cout, sales_data5) << std::endl;

	return 0;
}
           

練習7.12

把隻接受一個istream 作為參數的構造函數移到類的内部。

Sales_data_ex12.h

#ifndef SALES_DATA_H_
#define SALES_DATA_H_

#include <string>

struct Sales_data;

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

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(p*n){}
	Sales_data(std::istream &is) {read(is, *this);}
	std::string isbn() const {return bookNo;}
    Sales_data& combine(const Sales_data&);
    double avg_price() const;

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

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

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 << " " << item.avg_price();

	return os;
}

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

	return sum;
}

#endif
           

ex12.cpp

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

int main()
{
	Sales_data sales_data1;
	print(std::cout, sales_data1) << std::endl;

	Sales_data sales_data2("1-01");
	print(std::cout, sales_data2) << std::endl;

	Sales_data sales_data3("1-01", 1, 100);
	print(std::cout, sales_data3) << std::endl;

	Sales_data sales_data4(std::cin);
	print(std::cout, sales_data4) << std::endl;

	// Sales_data sales_data5();
	// print(std::cout, sales_data5) << std::endl;

	return 0;
}
           

練習7.13

使用istream構造函數重寫第229頁的程式。
#include <iostream>
#include <string>
#include "Sales_data_ex12.h"

int main()
{
    Sales_data total(std::cin);

    if (!total.isbn().empty())
    {
        Sales_data trans;

        while (read(std::cin, trans))
        {
            if (total.isbn() == trans.isbn())
            {
                total.combine(trans);
            }
            else
            {
                print(std::cout, total);
                std::cout << std::endl;
                total = trans;
            }
        }
        print(std::cout, total);
        std::cout << std::endl;

        return 0;
    }
    else
    {
        std::cerr << "No data?!" << std::endl;
        return -1;  // indicate failure
    }
}
           

練習7.14

編寫一個構造函數,令其用我們提供的類内初始值顯式地初始化成員。

練習7.15

為你的 Person 類添加正确的構造函數。
#ifndef PERSON_H_
#define PERSON_H_

#include <string>

struct Person;

std::istream &read(std::istream &is, Person &item);
std::ostream &print(std::ostream &os, const Person &item);

struct Person
{
	Person() : name(""), address(""){}
	Person(const std::string &sname, const std::string &saddress = "") : name(sname), address(saddress){}
	Person(std::istream &is){read(is, *this);}
    std::string get_name() const{return name;}
    std::string get_address() const{return address;}

    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.16

在類的定義中對于通路說明符出現的位置和次數有限定嗎?如果有,是什麼?什麼樣的成員應該定義在public 說明符之後?什麼樣的成員應該定義在private 說明符之後?

一個類可以包含0個或多個通路說明符,而且對于某個通路說明符能出現多少次也沒有嚴格的限定。

public:成員在整個程式内可被通路,public成員定義類的接口;

private:成員可以被類的成員函數通路,但是不能被使用該類的代碼通路,private部分封裝了(即隐藏了)類的實作細節。

練習7.17

使用class 和 struct 時有差別嗎?如果有,是什麼?

struct預設的通路權限是public;

class預設的通路權限是private。

練習7.18

封裝是何含義?它有什麼用處?

封裝是實作與接口的分離。它隐藏了類型的實作細節。(在C++中,封裝是通過将實作放在一個類的私有部分來實作的)

封裝有兩個重要的優點:

1.確定使用者代碼不會無意間破壞封裝對象的狀态;

2.被封裝的類的具體實作細節可以随時改變,而無須調整使用者級别的代碼。

練習7.19

在你的Person 類中,你将把哪些成員聲明成public 的?哪些聲明成private 的?解釋你這樣做的原因。
struct Person
{
public:
	Person() : name(""), address(""){}
	Person(const std::string &sname, const std::string &saddress = "") : name(sname), address(saddress){}
	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;
};
           

接口應該被定義為公共的,資料不應該暴露在類之外。

練習7.20

友元在什麼時候有用?請分别舉出使用友元的利弊。

類可以允許其他類或者函數通路它的非公有成員,方法是令其他類或者函數成為它的友元。

優點:

外部函數可以友善地使用類的成員,而不需要顯示地給它們加上類名;

可以友善地通路所有非公有成員;

有時,對類的使用者更容易讀懂。

缺點:

減少封裝和可維護性;

代碼冗長,類内的聲明,類外函數聲明。

練習7.21

修改你的Sales_data 類使其隐藏實作的細節。你之前編寫的關于Sales_data操作的程式應該繼續使用,借助類的新定義重新編譯該程式,確定其正常工作。

Sales_data_ex21.h

#ifndef SALES_DATA_H_
#define SALES_DATA_H_

#include <string>

struct Sales_data;

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

struct 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(p*n){}
	Sales_data(std::istream &is) {read(is, *this);}
	std::string isbn() const {return bookNo;}
    Sales_data& combine(const Sales_data&);
    double avg_price() const;
private:
    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;
}

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

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 << " " << item.avg_price();

	return os;
}

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

	return sum;
}

#endif
           

ex21.cpp

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

int main()
{
    Sales_data total(std::cin);

    if (!total.isbn().empty())
    {
        Sales_data trans;

        while (read(std::cin, trans))
        {
            if (total.isbn() == trans.isbn())
            {
                total.combine(trans);
            }
            else
            {
                print(std::cout, total);
                std::cout << std::endl;
                total = trans;
            }
        }
        print(std::cout, total);
        std::cout << std::endl;

        return 0;
    }
    else
    {
        std::cerr << "No data?!" << std::endl;
        return -1;  // indicate failure
    }
}
           

練習7.22

修改你的Person 類使其隐藏實作的細節。

Person_ex22.h

#ifndef PERSON_H_
#define PERSON_H_

#include <string>

struct Person;

std::istream &read(std::istream &is, Person &item);
std::ostream &print(std::ostream &os, const Person &item);

struct Person
{
friend std::istream &read(std::istream &is, Person &item);
friend std::ostream &print(std::ostream &os, const Person &item);
public:
	Person() : name(""), address(""){}
	Person(const std::string &sname, const std::string &saddress = "") : name(sname), address(saddress){}
	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
           

ex22.cpp

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

int main()
{
	Person person1;
	print(std::cout, person1) << std::endl;

	Person person2("tx", "hangzhou");
	print(std::cout, person2) << std::endl;
	std::cout << person2.get_name() << " " << person2.get_address() << std::endl;

	Person person3("tx");
	print(std::cout, person3) << std::endl;

	Person person4(std::cin);
	print(std::cout, person4) << std::endl;

	return 0;
}
           

練習7.23

編寫你自己的Screen 類型。
#ifndef SCREEN_H_
#define SCREEN_H_

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

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

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

#endif
           

練習7.25

Screen 能安全地依賴于拷貝和指派操作的預設版本嗎?如果能,為什麼?如果不能?為什麼?

能,Screen類中隻有内置類型和string可以使用拷貝和指派操作,見7.15。

練習7.26

将Sales_data::avg_price 定義成内聯函數。

Sales_data_ex26.h

#ifndef SALES_DATA_H_
#define SALES_DATA_H_

#include <string>

struct Sales_data;

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

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

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

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

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 << " " << item.avg_price();

	return os;
}

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

	return sum;
}

#endif
           

ex26.cpp

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

int main()
{
    Sales_data total(std::cin);

    if (!total.isbn().empty())
    {
        Sales_data trans;

        while (read(std::cin, trans))
        {
            if (total.isbn() == trans.isbn())
            {
                total.combine(trans);
            }
            else
            {
                print(std::cout, total);
                std::cout << std::endl;
                total = trans;
            }
        }
        print(std::cout, total);
        std::cout << std::endl;

        return 0;
    }
    else
    {
        std::cerr << "No data?!" << std::endl;
        return -1;  // indicate failure
    }
}
           

練習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_ex27.h

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

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 c)
{
	contents[r*width + col] = c;
	return *this;
}

#endif
           

ex27.cpp

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

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

	return 0;
}
           

練習7.28

如果move、set和display函數的傳回類型不是Screen& 而是Screen,則在上一個練習中獎會發生什麼?

傳回類型是Screen&的輸出:

XXXXXXXXXXXXXXXXXXXX#XXXX

XXXXXXXXXXXXXXXXXXXX#XXXX

傳回類型是Screen的輸出:

XXXXXXXXXXXXXXXXXXXX#XXXX

XXXXXXXXXXXXXXXXXXXXXXXXX

因為這樣的話move、set和display傳回的是Screen的臨時副本,後續set和display操作并不會改變myScreen。

練習7.29

修改你的Screen 類,令move、set和display函數傳回Screen并檢查程式的運作結果,在上一個練習中你的推測正确嗎?
$ ./ex29
XXXXXXXXXXXXXXXXXXXX#XXXX
XXXXXXXXXXXXXXXXXXXXXXXXX
           

練習7.30

通過this指針使用成員的做法雖然合法,但是有點多餘。讨論顯示使用指針通路成員的優缺點。

優點:

更明确,減少誤讀的可能性;

可以使用名稱與成員名相同的形參。

缺點:

備援代碼增加。

練習7.31

定義一對類X 和Y,其中X 包含一個指向 Y 的指針,而Y 包含一個類型為 X 的對象。
//
//  ex7_31.h
//  Exercise 7.31
//
//  Created by pezy on 11/17/14.
//

#ifndef CP5_ex7_31_h
#define CP5_ex7_31_h

class Y;

class X {
    Y* y = nullptr;
};

class Y {
    X x;
};

#endif
           

練習7.32

定義你自己的Screen 和 Window_mgr,其中clear是Window_mgr的成員,是Screen的友元。
#ifndef SCREEN_H_
#define SCREEN_H_

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

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



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 c)
{
	contents[r*width + col] = c;
	return *this;
}

void Window_mgr::clear(ScreenIndex i)
{
    Screen &s = screens[i];
    s.contents = std::string(s.height * s.width, ' ');
}

#endif
           

練習7.33

如果我們給Screen 添加一個如下所示的size成員将發生什麼情況?如果出現了問題,請嘗試修改它。
pos Screen::size() const
{
    return height * width;
}
           

Screen_ex33.h

#ifndef SCREEN_H_
#define SCREEN_H_

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

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){ }
    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);
    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;}
    pos size() 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::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 c)
{
	contents[r*width + col] = c;
	return *this;
}

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

void Window_mgr::clear(ScreenIndex i)
{
    Screen &s = screens[i];
    s.contents = std::string(s.height * s.width, ' ');
}

#endif
           

ex33.cpp

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

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

	return 0;
}
           

練習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(); // use `string`
class Exercise {
public:
    typedef double Type;
    Type setVal(Type); // use `double`
    Type initVal(); // use `double`
private:
    int val;
};

Type Exercise::setVal(Type parm) {  // first is `string`, second is `double`
    val = parm + initVal();     // Exercise::initVal()
    return val;
}
           

修改為:

Exercise::Type Exercise::setVal(Type parm) {
    val = parm + initVal();
    return val;
}
           

練習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);   // use Sales_data(std::istream &is) ; its value are up to your input.

int main() {
  Sales_data next;  // use Sales_data(std::string s = ""); bookNo = "", cnt = 0, revenue = 0.0
  Sales_data last("9-999-99999-9"); // use Sales_data(std::string s = ""); bookNo = "9-999-99999-9", cnt = 0, revenue = 0.0
}
           

練習7.38

有些情況下我們希望提供cin作為接受istream& 參數的構造函數的預設實參,請聲明這樣的構造函數。

練習7.39

如果接受string 的構造函數和接受 istream& 的構造函數都使用預設實參,這種行為合法嗎?如果不,為什麼?

非法。因為這樣的話,重載構造函數Sale_data()将不明确。

練習7.40

從下面的抽象概念中選擇一個(或者你自己指定一個),思考這樣的類需要哪些資料成員,提供一組合理的構造函數并闡明這樣做的原因。
(a) Book
(b) Data
(c) Employee
(d) Vehicle
(e) Object
(f) Tree
           
//by Mooophy
#include <iostream>
#include <string>

class Book 
{
public:
    Book(unsigned isbn, std::string const& name, std::string const& author, std::string const& pubdate)
        :isbn_(isbn), name_(name), author_(author), pubdate_(pubdate)
    { }

    explicit Book(std::istream &in) 
    { 
        in >> isbn_ >> name_ >> author_ >> pubdate_;
    }

private:
    unsigned isbn_;
    std::string name_;
    std::string author_;
    std::string pubdate_;
};
           

練習7.41

使用委托構造函數重新編寫你的Sales_data 類,給每個構造函數體添加一條語句,令其一旦執行就列印一條資訊。用各種可能的方式分别建立 Sales_data 對象,認真研究每次輸出的資訊直到你确實了解了委托構造函數的執行順序。

Sales_data_ex41.h

#ifndef SALES_DATA_H_
#define SALES_DATA_H_

#include <string>

struct Sales_data;

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

struct 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(const std::string &s, unsigned n, double p) : bookNo(s), units_sold(n), revenue(p*n){std::cout << "Sales_data(const std::string &s, unsigned n, double p)" << std::endl;}
	Sales_data() : Sales_data("", 0, 0){std::cout << "Sales_data() : Sales_data(\"\", 0, 0)" << std::endl;}
	Sales_data(const std::string &s) : Sales_data(s, 0, 0){std::cout << "Sales_data(const std::string &s) : Sales_data" << std::endl;}
	Sales_data(std::istream &is) : Sales_data(){read(is, *this);std::cout << "Sales_data(std::istream &is) : Sales_data()" << std::endl;}
	std::string isbn() const {return bookNo;}
    Sales_data& combine(const Sales_data&);
private:
	inline double avg_price() const;

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

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

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 << " " << item.avg_price();

	return os;
}

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

	return sum;
}

#endif
           

ex41.cpp

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

int main()
{
    Sales_data sales_data1("001-01", 1, 100);
    Sales_data sales_data2;
    Sales_data sales_data3("001-02");
    Sales_data sales_data4(std::cin);

    return 0;
}
           

執行結果如下:

$ ./ex41 
Sales_data(const std::string &s, unsigned n, double p)
Sales_data(const std::string &s, unsigned n, double p)
Sales_data() : Sales_data("", 0, 0)
Sales_data(const std::string &s, unsigned n, double p)
Sales_data(const std::string &s) : Sales_data
Sales_data(const std::string &s, unsigned n, double p)
Sales_data() : Sales_data("", 0, 0)
001-03 1 100
Sales_data(std::istream &is) : Sales_data()
           

練習7.42

對于你在練習7.40中編寫的類,确定哪些構造函數可以使用委托。如果可以的話,編寫委托構造函數。如果不可以,從抽象概念清單中重新選擇一個你認為可以使用委托構造函數的,為挑選出的這個概念編寫類定義。
#include <iostream>
#include <string>

class Book 
{
public:
    Book(unsigned isbn, std::string const& name, std::string const& author, std::string const& pubdate)
        :isbn_(isbn), name_(name), author_(author), pubdate_(pubdate)
    { }

    explicit Book(std::istream &in) 
    { 
        in >> isbn_ >> name_ >> author_ >> pubdate_;
    }

private:
    unsigned isbn_;
    std::string name_;
    std::string author_;
    std::string pubdate_;
};
           

練習7.43

假定有一個名為 NoDefault 的類,它有一個接受 int 的構造函數,但是沒有預設構造函數。定義類 C,C 有一個 NoDefault 類型的成員,定義C 的預設構造函數。
class NoDefault {
public:
    NoDefault(int i) { }
};

class C {
public:
    C() : def(0) { } // define the constructor of C.
private:
    NoDefault def;
};
           

練習7.44

下面這條聲明合法嗎?如果不,為什麼?

非法,因為NoDefault沒有預設構造函數。

練習7.45

如果在上一個練習中定義的vector的元素類型是C,則聲明合法嗎?為什麼?

合法,因為C有預設構造函數。

練習7.46

下面哪些論斷是不正确的?為什麼?
(a) 一個類必須至少提供一個構造函數。
(b) 預設構造函數是參數清單為空的構造函數。
(c) 如果對于類來說不存在有意義的預設值,則類不應該提供預設構造函數。
(d) 如果類沒有定義預設構造函數,則編譯器将為其生成一個并把每個資料成員初始化成相應類型的預設值。
           

(a)不正确,沒有構造函數時,有時可以生成預設構造函數;

(b)不正确,預設構造函數是沒有構造函數的情況下,由編譯器生成的構造函數;

(c)不正确,預設構造函數在一些情況下非常重要;

(d)不正确,當類沒有顯示地定義構造函數時,編譯器才會隐式地定義預設構造函數。

練習7.47

說明接受一個string 參數的Sales_data構造函數是否應該是explicit的,并解釋這樣做的優缺點。

優點:

防止隐式轉換的産生;

可以隻用作初始化。

缺點:

隻有個單個參數的構造函數才有意義。

練習7.48

假定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)正确;

(b)不正确,combine的參數是非常量的引用,是以我們不能将臨時參數傳遞給它,改成Sales_data &combine(const Sales_data&);後正确;

(c)不正确,後面的const不對,this需要可改變的。

練習7.50

确定在你的Person 類中是否有一些構造函數應該是 explicit 的。
#ifndef PERSON_H_
#define PERSON_H_

#include <string>

struct Person;

std::istream &read(std::istream &is, Person &item);
std::ostream &print(std::ostream &os, const Person &item);

struct Person
{
friend std::istream &read(std::istream &is, Person &item);
friend std::ostream &print(std::ostream &os, const Person &item);
public:
	Person() : name(""), address(""){}
	Person(const std::string &sname, const std::string &saddress = "") : name(sname), address(saddress){}
	explicit 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.51

vector 将其單參數的構造函數定義成 explicit 的,而string則不是,你覺得原因何在?

以下函數:

如果vector的構造函數沒有explicit,

我們就會不明白上述函數的意思。

stirng則不同,下述函數我們就很清楚。

void setYourName(std::string); // declaration.
setYourName("pezy"); // just fine.
           

練習7.52

使用2.6.1節的 Sales_data 類,解釋下面的初始化過程。如果存在問題,嘗試修改它。

該初始化使用花括号括起來的成員初始值清單來初始化聚合類的資料成員。是以我們需要定義聚合類:

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

練習7.53

定義你自己的 Debug。
#ifndef DEBUG_EX53_
#define DEBUG_EX53_

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 amy() { 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;}
private:
	bool hw;
	bool io;
	bool other;
};

#endif
           

練習7.54

Debug中以 set_ 開頭的成員應該被聲明成 constexpr 嗎?如果不,為什麼?

在C++11中,constexpr函數時隐式的const,将不能更改資料成員;C++14中沒有這個特點。

練習7.55

7.5.5節的 Data 類是字面值常量類嗎?請解釋原因。

不是,std::string不是字面值類型。

#include <string>
#include <iostream>
#include <type_traits>

struct Data {
    int ival;
    std::string s;
};

int main()
{
    std::cout << std::boolalpha;
    std::cout << std::is_literal_type<Data>::value << std::endl;
    // output: false
}
           

練習7.56

什麼是類的靜态成員?它有何優點?靜态成員與普通成員有何差別?

類的靜态成員與類本身直接相關,而不是與類的各個對象保持關聯。

每個對象不需要存儲公共資料,如果資料被改變,則每個對象都可以使用新值。

靜态資料成員可以是不完全類型;

可以使用靜态成員作為預設實參。

練習7.57

編寫你自己的 Account 類。
#ifndef ACCOUNT_EX57_H_
#define ACCOUNT_EX57_H_

#include <string>

class Account
{
public:
	void calculate() {amount += amount * interestRate;}
	static double rate(){return interestRate;}
	static void rate(double newRate){ interestRate = newRate;}
private:
	std::string owner;
	double amount;
	static double interestRate;
	static double initRate(){return 4.0;}
};
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;
           
// example.h
class Example {
public:
    static double rate;
    static const int vecSize = 20;
    static vector<double> vec;
};

// example.C
#include "example.h"
double Example::rate = 6.5;
vector<double> Example::vec(vecSize);
//vector<double> Example::vec(Example::vecSize);
           

繼續閱讀