C++ Primer(第五版)|練習題答案與解析(第七章:類)

C++ Primer(第五版)|練習題答案與解析(第七章:類)

本博客主要記錄C++ Primer(第五版)中的練習題答案與解析。
參考:C++ Primer
C++ Primer

練習題7.1

利用2.6.1節所定義的Sales_data類爲1.6節的交易處理程序編寫一個新的版本。

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

爲上題中的Sales_data類添加combine和isbn成員。

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

修改7.1.1節中的交易程序,令其使用這些成員。

#include <iostream>
#include <vector>
#include <string>
#include <initializer_list>
using namespace std;
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;
}
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.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?!" << std::endl;
        return -1;
    }
    return 0;
}

測試

ISN10123 5 5
ISN10123 1 10
^Z
ISN10123 6 15

練習題7.4

編寫一個名爲Person的類,使其表示人員的姓名和住址。使用string對象存放這些元素,接下來的練習將不斷充實這個類的其他特徵。

#include <string>
class Person {
    std::string name;
    std::string address;
};

練習題7.5

在你的Person類中提供一些操作使其能夠返回姓名和住址。這些函數是否應該是const呢?解釋原因。

class Person
{
public:
​    std::string name;
​    std::string address;
​    std::string getName() const { return name; }
​    std::string getAddr() const { return address; }
};

要使用const,因爲這些函數並不改變它調用的對象的內容。

練習題7.6

對於函數add、read和print,定義你自己的版本。

#include <string>
#include <iostream>
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;
};
// member functions.
Sales_data& Sales_data::combine(const Sales_data& rhs)
{
    units_sold += rhs.units_sold;
    revenue += rhs.revenue;
    return *this;
}
// nonmember functions
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;
}

練習題7.7

使用這些新函數重寫7.1.2節中的交易處理程序。

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

測試

ISN101023 5 10
ISN101023 1 10
^Z
ISN101023 6 60

練習題7.8

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

因爲print函數不會改變對象的值,但是read函數則會改變對象內容。P234

練習題7.9

對於7.1.2節練習中的代碼,添加讀取和打印Person對象的操作。

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

練習題7.10

在下面這條if語句中,條件部分的作用是什麼?
if (read (read(cin, data), data))

一次連續讀入兩個data。read(cin, data)返回的還是cin的引用,在該輸入流上繼續讀入數據。

練習題7.11

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

#include <iostream>
#include <vector>
#include <string>
#include <initializer_list>
using namespace std;
#include <string>
#include <iostream>
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;
};

// nonmember functions
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;
}

// member functions.
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;
}
int main()
{
    Sales_data item1;
    print(std::cout, item1) << std::endl;
    Sales_data item2("0-201-78345-X");
    print(std::cout, item2) << std::endl;
    Sales_data item3("0-201-78345-X", 3, 20.00);
    print(std::cout, item3) << std::endl;
    Sales_data item4(std::cin);
    print(std::cout, item4) << std::endl;
    return 0;
}

測試:

 0 0
0-201-78345-X 0 0
0-201-78345-X 3 60
ISN01023 5 7
ISN01023 5 35

練習題7.12

把只接受一個istream作爲參數的構造函數定義到類的內部。

struct Sales_data;
std::istream& read (std::istream& is, Sales_data& item);
struct Sales_data
{
    std::string bookNo; // 對象的ISBN編號
    unsigned units_sold = 0; // 售出的冊數
    double revenue = 0.0; // 總價格
    Sales_data() = default;
    Sales_data(const std::string &s) : bookNo(s){}
    Sales_data(const std::string &s, unsigned n, double r) : bookNo(s), units_sold(n), revenue(r) {}
    Sales_data(std::istream& is){ read(is, *this); };
    std::string isbn() const { return bookNo; } // 返回ISBN編號
    Sales_data& combine (const Sales_data& rhs); // 將一個Sales_data對象加到另一個上面
};

練習題7.13

把只接受一個istream作爲參數的構造函數定義到類的內部。

int main()
{
    Sales_data total(std::cin);
    if (!total.isbn().empty())
    {
        std::istream &is = std::cin;
        while (is) {
            Sales_data trans(is);
            if (!is) break;
            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;
}

使用上一題的頭文件,測試:

ISN10123 5 6
ISN10123 2 10
^Z
ISN10123 7 50

練習題7.14

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

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

練習題7.15

爲你的Person類添加正確的構造函數。

#include <string>
#include <iostream>
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;
};
std::istream &read(std::istream &is, Person &person)
{
    is >> person.name >> person.address;
    return is;
}
std::ostream &print(std::ostream &os, const Person &person)
{
    os << person.name << " " << person.address;
    return os;
}

練習題7.16

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

一個類對訪問說明符出現的次數和位置並沒有嚴格的限定。構造函數和接口函數定義在public之後,而數據成員和部分成員函數定義在private後面。P241

練習題7.17

使用class和struct時有什麼區別?

class和struct唯一的區別就是默認訪問權限不同,class默認訪問權限爲private,而struct則是public。P241

練習題7.18

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

封裝就是定義一系列的接口,對用戶隱藏實現細節,用戶在使用時只需要調用接口就可以。P240

練習題7.19

在Person類中,將哪些成員聲明成public?哪些聲明成private?

Person類的構造函數和獲取信息等函數應該設置爲public,成員數據設置爲private。因爲構造函數和獲取信息的函數需要在類外進行調用,而成員數據可以封裝成接口,不需要暴露給用戶。

練習題7.20

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

友元是類提供給非成員函數訪問類內私有成員的一種機制。優勢是:讓類外函數也可以像類內成員一樣方便的訪問私有成員。缺點是:破壞了類的封裝,寫法較麻煩,必須在類內類外都進行聲明。P241

練習題7.21

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

// 對Sales_data類做如下改動
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(Sales_data* item1, Sales_data& item2);

public:

    Sales_data() = default;
    Sales_data(const std::string &s) : bookNo(s){}
    Sales_data(const std::string &s, unsigned n, double r) : bookNo(s), units_sold(n), revenue(r) {}
    Sales_data(std::istream& is){ read(is, *this); };

    std::string isbn() const { return bookNo; } // 返回ISBN編號
    Sales_data& combine (const Sales_data& rhs); // 將一個Sales_data對象加到另一個上面
private:
    std::string bookNo; // 對象的ISBN編號
    unsigned units_sold = 0; // 售出的冊數
    double revenue = 0.0; // 總價格
};

std::istream& read (std::istream& is, Sales_data& item);
std::ostream& print (std::ostream& os, const Sales_data& item);
Sales_data& add(Sales_data* item1, Sales_data& item2);

練習題7.22

修改你的Person類使其隱藏實現細節。

#include <string>
#include <iostream>
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;
};
std::istream &read(std::istream &is, Person &person)
{
    is >> person.name >> person.address;
    return is;
}
std::ostream &print(std::ostream &os, const Person &person)
{
    os << person.name << " " << person.address;
    return os;
}

練習題7.23

編寫你自己的Screen類。

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

練習題7.24

給你的Screen類添加三個構造函數:一個默認構造函數;另一個構造函數接受寬和高的值,然後將contents初始化成給定數量的空白;第三個參數接受寬和高的值以及一個字符,該字符作爲初始化之後屏幕的內容。

#include <string>
class Screen {
    public:
        using pos = std::string::size_type;
        Screen() = default; // 1
        Screen(pos ht, pos wd):height(ht), width(wd), contents(ht*wd, ' '){ } // 2
        Screen(pos ht, pos wd, char c):height(ht), width(wd), contents(ht*wd, c){ } // 3
        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

Screen能安全的依賴拷貝和賦值操作的默認版本嗎?如果能,爲什麼?如果不能,爲什麼?

可以依賴默認拷貝和賦值操作,因爲類中沒有分配內存的操作。

練習題7.26

將Sales_data::avg_price定義成內聯函數。

在Sales_data類中的public下添加下面語句:inline double avg_price() const { return units_sold ? revenue/units_sold : 0; }

練習題7.27

給你自己的Screen類添加move、set和display函數,通過執行下面的代碼檢驗你的類是否正確。

#include <iostream>
#include <vector>
#include <string>
#include <initializer_list>
using namespace std;
class Screen {
public:
    using pos = std::string::size_type;
    Screen() = default; // 1
    Screen(pos ht, pos wd):height(ht), width(wd), contents(ht*wd, ' '){ } // 2
    Screen(pos ht, pos wd, char c):height(ht), width(wd), contents(ht*wd, c){ } // 3
    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(6, 6, 'O');
    myScreen.move(4, 0).set('X').display(std::cout);
    std::cout << "\n";
    myScreen.display(std::cout);
    std::cout << "\n";

    return 0;
}

測試

OOOOOOOOOOOOOOOOOOOOOOOOXOOOOOOOOOOO
OOOOOOOOOOOOOOOOOOOOOOOOXOOOOOOOOOOO

練習題7.28

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

如果都返回的是Screen,則move和set都修改的是副本,原始的myScreen並沒有被修改,myScreen.display(cout);語句最終打印出來還是(6, 6, ‘O’’)的值。

練習題7.29

修改程序,令move、set和display函數的返回類型是Screen,驗證上題中你的猜測。

修改後:

OOOOOOOOOOOOOOOOOOOOOOOOXOOOOOOOOOOO//myScreen.move(4, 0).set('X').display(std::cout);
OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO//myScreen.display(std::cout);

練習題7.30

通過this指針使用成員的做法雖然合法,但是有點多餘。討論顯式地使用指針訪問成員的優缺點。

優點:

  • 1、使程序意圖明確,更易讀;
  • 2、可以使形參名和要賦值的成員名相同。
    如:std::string& setName(const string& name) { this->name = name; }

缺點:有些場景下比較多餘,std::string const& getName() const { return this->name; }

練習題7.31

定義一對類X和Y,其中X包含一個指向Y的指針,而Y包含一個類型位X的對象。

class X;
class Y;
class X
{
private:
    Y* y = nullptr;
};
class Y
{
private:
    X x;
};

練習題7.32

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

// 要遵守定義友元函數的依賴關係
class Screen;
class Window_mgr
{
public:
    using ScreenIndex = std::vector<Screen>::size_type;
    void clear(ScreenIndex);
private:
    std::vector<Screen> screens;    // 這裏不能調用Screen構造函數,否則報錯引用了不完全類型
};
class Screen
{
    friend void Window_mgr::clear(ScreenIndex);
    /* Screen類的其他定義 */
};
void Window_mgr::clear(ScreenIndex i)
{
    Screen &s = screens[i];
    s.contents = string(s.height * s.width, ' ');
}

練習題7.33

如果我們給Screen添加一個如下所示的size成員將會發生什麼情況?如果出來問題,請嘗試修改它。

pos Screen::size() const
{
    return height * width;
}
// 將會發生提示pos是不知道的類型。應做如下修改:
Screen::pos Screen::size() const

練習題7.34

如果我們把P256的Screen類中的pos的typedef放在類的最後一行會發生什麼情況?

將會提示pos是不知道的類型。

練習題7.35

介紹下面代碼的含義,說明其中的Type和initVal分別使用了哪個定義。如果代碼存在錯誤,嘗試修改它。

typedef string Type;
Type initVal();
class Exercise {
public:
    typedef double Type;
    Type setVal(Type);              // double
    Type initVal();                 // double
private:
    int val;
};
Type Exercise::setVal(Type parm) {      // 返回的Type是string類型,形參類型是double類型
    val = parm + initVal;               // initVal調用的類中的
    return val;
}
// 會報錯Type Exercise::setVal(Type parm)匹配不到類中的函數,因爲返回值與類中不一致。改爲:
// initVal函數只聲明未定義,也會報錯。
Exercise::Type Exercise::setVal(Type parm){}

練習題7.36

介紹下面代碼的含義,說明其中的Type和initVal分別使用了哪個定義。如果代碼存在錯誤,嘗試修改它。

struct X {
    X (int i, int j):base(i), rem(base % j) {}
    int 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 = "", cnt = 0, revenue = 0.0
  Sales_data last("9-999-99999-9"); // 使用了 Sales_data(std::string s = ""); bookNo = "9-999-99999-9", cnt = 0, revenue = 0.0
}

練習題7.38

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

可以寫爲:Sales_data(std::istream &is = std::cin) { read(is, *this); }P260

練習題7.39

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

不合法,如果都使用默認值,不提供實參,則編譯器就不知道該調用哪個構造函數了。

練習題7.40

從下面的抽象概念中選擇一個,思考這樣的類需要哪些數據成員,提供一組合理的構造函數並闡明原因。

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

使用委託構造函數重新編寫你的Sales_data類,給每個構造函數體添加一條語句,令其一旦執行就打印一條信息。用各種可能的方式分別創建Sales_data對象,認真研究每次輸出的信息直到你確實理解了委託構造函數的執行順序。

#include <iostream>
#include <vector>
#include <string>
#include <initializer_list>
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(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(n*p)
    { std::cout << "Sales_data(const std::string&, unsigned, double)" << std::endl; }

    Sales_data() : Sales_data("", 0, 0.0f)
    { std::cout << "Sales_data()" << std::endl; }

    Sales_data(const std::string &s) : Sales_data(s, 0, 0.0f)
    { std::cout << "Sales_data(const std::string&)" << std::endl; }

    Sales_data(std::istream &is):Sales_data()//Sales_data(istream& is)是一個委託構造函數,它委託給了默認構造函數Sales_data()
    {std::cout << "Sales_data(const std::string&)" << std::endl;};

    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;
}
// declarations for nonmember parts of the Sales_data interface.
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);
using namespace std;
int main()
{
    cout << "1. default way: " << endl;
    cout << "----------------" << endl;
    Sales_data s1;
    cout << "\n2. use std::string as parameter: " << endl;
    cout << "----------------" << endl;
    Sales_data s2("CPP-Primer-5th");
    cout << "\n3. complete parameters: " << endl;
    cout << "----------------" << endl;
    Sales_data s3("CPP-Primer-5th", 3, 25.8);
    cout << "\n4. use istream as parameter: " << endl;
    cout << "----------------" << endl;
    Sales_data s4(std::cin);
    return 0;
}

測試:

1. default way:
----------------
Sales_data(const std::string&, unsigned, double)
Sales_data()

2. use std::string as parameter:
----------------
Sales_data(const std::string&, unsigned, double)
Sales_data(const std::string&)

3. complete parameters:
----------------
Sales_data(const std::string&, unsigned, double)

4. use istream as parameter:
----------------
Sales_data(const std::string&, unsigned, double)
Sales_data()
Sales_data(const std::string&)

練習題7.43

假定有一個類名爲NoDefault的類,它有一個接受int的構造函數,但是沒有默認構造函數。定義類c,c有一個NoDefault類型的成員,定義c的默認構造函數。

class NoDefault {
public:
    NoDefault(int i) { }
};
class C {
public:
    C() : def(0) { } // 定義C的構造函數.
private:
    NoDefault def;
};

練習題7.44

下面這條聲明合理嗎?爲什麼?
vector vec(10);

非法,因爲vector的10個元素沒有被初始化,因此需要默認初始化,而NoDefault類型沒有提供默認構造函數

練習題7.45

如果上題中定義的vector類型是C,則聲明合法嗎?

合法,因爲C類提供了默認構造函數。

練習題7.46

下面哪種論斷是不正確的?爲什麼?
1.一個類必須至少提供一個構造函數。
2.默認構造函數是參數列表爲空的構造函數。
3.如果對於類來說不存在有意義的默認值,則類不應該提供默認構造函數。
4.如果類沒有定義默認構造函數,則編譯器將爲其生成一個並把每個數據成員初始化成相應類型的默認值。

均不正確:

  • 1.類可以不提供構造函數,編譯器會提供一個默認構造函數。
  • 2.默認構造函數爲沒有初始化列表(而不是參數列表爲空)的對象提供默認初始值,爲成員提供默認值的構造函數也稱爲默認構3.造函數。
  • 3.類應該提供默認構造函數。
  • 4.只有當類沒有定義任何構造函數的時候,編譯器纔會定義默認構造函數。

練習題7.47

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

Sales_data類的構造函數應該是explicit的。

優點:

  • 保證用戶能按照類設計者的初衷進行初始化。

缺點:

  • 當只有一個參數時,要進行初始化再使用,沒有隱式轉換的寫法簡潔。

練習題7.48

假定Sales_data的構造函數不是explicit的,下述定義將會執行什麼操作?

string null_isbn("9-99-9999-9");
Sales_data item1(null_isbn);        // 用string類型的null_isbn直接初始化item1。
Sales_data item2("9-99-9999-9");        // 用字符串初始化item2,爲Sales_data 類型。

練習題7.49

對於combine函數的三種不同聲明,當我們調用i.combine(s)時分別發生什麼情況?其中i是一個Sales_data,而s是一個string對象。

(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類型的引用
(c)Sales_data &combine(const Sales_data&) const;報錯:error: assignment of member ‘Sales_data::units_sold’ in read-only object,聲明最後的const會禁止函數對值做出改變。

練習題7.50

確定在你的person類中是否有一些構造函數應該是explicit的。

 explicit Person (std::istream& is) { readPerson(is, *this); }
// 只接受一個參數的構造函數可以是explicit

練習題7.51

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

https://blog.csdn.net/misayaaaaa/article/details/56479195
“定義爲explicit是爲了防止隱式的類型轉換。”

int getSize(const std::vector<int>&);
//這樣的使用是否顯得比較迷惑
getSize(34);

練習題7.52

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

Sales_data item = {"978-059035", 15, 29.98};
// 要使用這種初始化方式,要求類必須是聚合類。因此Sales_data類需要改成如下形式:
struct Sales_data
{
    std::string bookNo;
    unsigned int unit_sold;
    double revenue;
};

練習題7.53

定義你自己的Debug。

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;        // io錯誤
    bool other;     // 其它錯誤
};

練習題7.54

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

在c++11中聲明函數是constexpr必須滿足以下條件(P267):

  • 返回值和參數必須是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’

練習題7.55

7.5.5節(P256)的Data類是字面值常量類嗎?爲什麼?

數據成員都是字面值類型的聚合類是字面值常量類。 但Data類的數據成員不一定是字面值類型,使用變量或表達式也可以進行初始化。

練習題7.56

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

類的靜態成員與類本身直接相關,而不是與類的各個對象關聯。P268
優點:每個對象都不需要單獨存儲靜態成員變量,一旦靜態成員改變了,則每個對象都可以使用新的值。
區別:類的靜態成員屬於類本身,在類加載時就會分配內存,可以通過類名直接進行訪問。
普通成員屬於類的對象,只有在類對象產生時纔會分配內存。只能通過對象去訪問。

練習題7.57

編寫你自己的Account類。

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

下面的靜態數據成員的聲明和定義有錯嗎?請解釋原因。

// example.h
class Example
{
 public:
    static double rate = 6.5;       // 錯誤,靜態成員類內初始化應該是一個const表達式。
    // 改爲 static constexpr double rete = 6.5;
    static const int vecSize = 20;
    static vector<double> vec(vecSize); // vector不需要在類內就定義大小 P270
    // 改爲static vector<double> vec;
}
// examplec.cpp
#include "example.h"
double Example::rate;
vector<double> Example::vec;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章