C++ primer 第七章習題

chapter7 類

練習

7.1.1 節練習

練習7.1

  • 使用2.6.1節練習定義的Sales_data類爲1.6節(第21頁)的交易處理程序編寫一個新版本。
#include <iostream>
#include "Sales_data.h"
using namespace std;
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.AddData(trans);
			else {
				total.Print();
				total = trans;
			}
		}
		total.Print();
	}else {
		cout << "No data?!" << endl;
		return -1;
	}return 0;
}

7.1.2 節練習

練習7.2

  • 曾在2.6.2節的練習(第76頁)中編寫了一個Sales_data類,請向這個類添加combine和isbn成員。
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 &rhs){
        units_sold += rhs.units_sold;
        revenue += rhs.revenue;
        return *this;
    }
}

練習7.3

  • 修改7.1.1節(第229頁)的交易處理程序,令其使用這些成員。
#include <iostream>
#include "Sales_data.h"
using namespace std;
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 {
				total.Print();
				total = trans;
			}
		}
		total.Print();
	}
	else {
		cout << "No data?!" << endl;
		return -1;
	}return 0;
}

練習7.4

  • 編寫一個名爲Person的類,使其表示人員的姓名和住址。使用string對象存放這些元素,接下來的練習將不斷充實這個類的其他特徵。
#include <string>
using namespace std;
struct Peason{
	string Name;
	string Address;
};

練習7.5

  • 在你的Person類中提供一些操作使其能夠返回姓名和地址。這些函數是否應該是const的呢?解釋原因。
#include <string>
using namespace std;
struct Peason{
	string name;
	string address;
	string get_name() const { return name; }
	string get_address() const { return address; }
};

應該是const的,因爲它們不會對類內內容進行任何修改,因此被定義爲const成員函數更安全。

7.1.3節練習

練習7.6

  • 對於函數add、read、和print,定義你自己的版本。
istream &read(istream &is, Sales_data &item) {
	double price = 0;
	is >> item.bookNo >> item.units_sold >> price;
	item.revenue = item.units_sold * price;
	return is;
}

ostream &print(ostream &os, const Sales_data &item) {
	os << item.bookNo << " " << 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節(第233頁)練習中的交易處理程序。
#include <iostream>
#include "Sales_data.h"
using namespace std;
int main()
{
	Sales_data total;
	if (read(cin, total)) {
		Sales_data trans;
		while (read(cin, trans)) {
			if (total.isbn() == trans.isbn())
				total.combine(trans);
			else {
				print(cout, total) << endl;
				total = trans;
			}
		}
		print(cout, total) << endl;
	}
	else {
		cout << "No data?!" << endl;
		return -1;
	}return 0;
}

練習7.8

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

因爲read函數使用時,需要將輸入流中獲取到的數據複製給引用的對象,會改變其值。而print函數只需要獲取其中的參數值,而不會對其有任何的修改。

會修改時,使用引用,獲取修改權限;不修改時,使用常量引用,保護變量。

練習7.9

  • 對於7.1.2節(第233頁)練習中的代碼,添加讀取和打印Person對象的操作。
istream &read(istream &is, Peason &item) {
	is >> item.name >> item.address;
	return is;
}
ostream &print(ostream &os, const Peason &item) {
	os << item.name << " " << item.address;
	return os;
}

練習7.10

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

因爲read函數返回的類型是引用,可以返回結果可以作爲實參被read函數繼續使用。連續讀取兩個對象data1, data2。如果讀取成功,條件判斷正確, 否則條件判斷錯誤。

7.1.4節練習

練習7.11

  • 在你的Sales_data類中添加構造函數,然後編寫一段程序令其用到每個構造函數。
#include <iostream>
#include <string>
using namespace std;
struct Sales_data {
	std::string bookNo;
	unsigned units_sold = 0;
	double revenue = 0.0;

	Sales_data() = default;
	Sales_data(const string &s) :bookNo(s) { }
	Sales_data(const string &s, unsigned n, double d) :bookNo(s), units_sold(n), revenue(n*d) { }
	Sales_data(istream &is);
    /*...*/
}
istream &read(istream &is, Sales_data &item) {
	double price = 0;
	is >> item.bookNo >> item.units_sold >> price;
	item.revenue = item.units_sold * price;
	return is;
}
Sales_data::Sales_data(istream &is) { read(is, *this); }
////////////////main()
#include <iostream>
#include "Sales_data.h"
using namespace std;
int main()
{
	Sales_data first;
	Sales_data second("123");
	Sales_data third("1234", 3, 2.22);
	Sales_data forth(cin);
	print(print(print(print(cout, first), second), third), forth);
}

練習7.12

  • 把只接受一個istream 作爲參數的構造定義函數移到類的內部。
#include <iostream>
#include <string>
using namespace std;
struct Sales_data;
istream &read(istream &is, Sales_data &item);

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

	Sales_data(istream &is) { read(is, *this); }
}
//將Sales_data和read函數提前聲明。

練習7.13

  • 使用istream構造函數重寫第229頁的程序。
#include <iostream>
#include "Sales_data.h"
using namespace std;
int main()
{
	Sales_data total(cin);
	if (cin) {
		Sales_data trans(cin);
		do {
			if (total.isbn() == trans.isbn())
				total.combine(trans);
			else {
				print(cout, total) << endl;
				total = trans;
			}
		} while (read(cin, trans));    //利用do while循環來完成構造函數後的第一次比較
		print(cout, total) << endl;
	}
	else {
		cout << "No data?!" << endl;
		return -1;
	}return 0;
}

練習7.14

  • 編寫一個構造函數,令其用我們提供的類內初始值顯式地初始化成員。
Sales_data(): bookNo("123"), unsigned(0), revenue(0.0) {}

練習7.15

  • 爲你的 Person 類添加正確的構造函數。
#include <iostream>
#include <string>
using namespace std;
struct Peason{
	string name;
	string address;
	Peason() = default;
	Peason(const string s1, const string s2) :name(s1), address(s2) { }
	Peason(istream &is);
}
istream &read(istream &is, Peason &item) {
	is >> item.name >> item.address;
	return is;
}
Peason::Peason(istream &is) {
	read(is, *this);
}

7.2節練習

練習7.16

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

在類的定義中對於訪問說明符出現的位置和次數沒有限制。

一般來說,構造函數和部分成員函數作爲接口程序,應該定義在public說明符之後。而數據成員和作爲實現部分的函數則應該被定義在private說明符之後。

練習7.17

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

有區別。兩者的默認訪問權限不同。class關鍵字下,定義在第一個訪問說明符之前的成員是private的,而在struct關鍵字下,定義在第一個訪問說明符之前的成員則是public的。

練習7.18

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

封裝的含義是隱藏了內部的實現細節,僅僅將接口提供給其他函數調用或訪問。

封裝的優點,一是確保用戶代碼不會無意間破壞封裝對象的狀態。二是被封裝的類的具體實現細節可以隨時改變,而無需調整用戶級別的代碼。

練習7.19

  • 在你的Person類中,你將把哪些成員聲明成public的?哪些聲明成private的?解釋你這樣做的原因。

將數據成員聲明成private,避免數據被外部修改。將成員函數聲明成public,供外部調用。

7.2.1節練習

練習7.20

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

友元在有其他類或函數訪問它的非公有成員時,可以令其獲得訪問的權限。但可能破壞類的封裝。

練習7.21

  • 修改你的Sales_data類使其隱藏實現的細節。你之前編寫的關於Sales_data操作的程序應該繼續使用,藉助類的新定義重新編譯該程序,確保其工作正常。
#include <iostream>
#include <string>
using namespace std;
class Sales_data;
istream &read(istream &is, Sales_data &item);

class Sales_data {
private:
	std::string bookNo;
	unsigned units_sold = 0;
	double revenue = 0.0;
public:
	Sales_data() = default;
	Sales_data(const string &s) :bookNo(s) { }
	Sales_data(const string &s, unsigned n, double d) :bookNo(s), units_sold(n), revenue(n*d) { }
	Sales_data(istream &is) { read(is, *this); }
	void CalcRevenue(double price);
	double CalcAveragePrice();
	void SetData(Sales_data data);
	void AddData(Sales_data data);
	std::string isbn() const { return bookNo; }
	Sales_data& combine(const Sales_data &rhs) {
		units_sold += rhs.units_sold;
		revenue += rhs.revenue;
		return *this;
	}
	friend istream &read(istream &is, Sales_data &item);
	friend ostream &print(ostream &os, const Sales_data &item);
};
/*...*/

練習7.22

  • 修改你的Person類使其隱藏實現的細節。
#include <iostream>
#include <string>
using namespace std;
class Peason{
public:
	Peason() = default;
	Peason(const string s1, const string s2) :name(s1), address(s2) { }
	Peason(istream &is);
	string get_name() const { return name; }
	string get_address() const { return address; }
private:
	string name;
	string address;
	friend istream &read(istream &is, Peason &item);
	friend ostream &print(ostream &os, const Peason &item);
};
istream &read(istream &is, Peason &item) {
	is >> item.name >> item.address;
	return is;
}
ostream &print(ostream &os, const Peason &item) {
	os << item.name << " " << item.address;
	return os;
}
Peason::Peason(istream &is) {
	read(is, *this);
}

7.3.1節練習

練習7.23

  • 編寫你自己的Screen類。
#include <string>
using namespace std;
class Screen {
public:
	typedef string::size_type pos;
	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 row, pos col) { return contents[row * width + col]; }
private:
	pos cursor = 0;
	pos height = 0, width = 0;
	string contents;
};

練習7.24

  • 給你的Screen類添加三個構造函數:一個默認構造函數;另一個構造函數接受寬和高的值,然後將contents初始化成給定數量的空白;第三個構造函數接受寬和高的值以及一個字符,該字符作爲初始化後屏幕的內容。
#include <string>
using namespace std;
class Screen {
public:
	typedef string::size_type pos;
	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]; }
	char get(pos row, pos col) { return contents[row * width + col]; }
private:
	pos cursor = 0;
	pos height = 0, width = 0;
	string contents;
};

練習7.25

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

Screen類中的四個成員對象都是內置類型,因此能夠安全地依賴於拷貝和賦值操作的默認版本。

練習7.26

  • 將Sales_data::avg_price定義成內聯函數。
inline double Sales_data::avg_prive() const
{
	if (units_sold != 0)
		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";
    
#include <string>
using namespace std;
class Screen {
public:
	typedef string::size_type pos;
	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]; }
	char get(pos row, pos col) { return contents[row * width + col]; }
	Screen &move(pos, pos);
	Screen &set(char);
	Screen &set(pos, pos, char);
	Screen &display(ostream &os) { do_display(os); return *this; }
	const Screen &display(ostream &os) const { do_display(os); return *this; }
private:
	pos cursor = 0;
	pos height = 0, width = 0;
	string contents;
	void do_display(ostream &os) const { os << contents; }
};
inline Screen &Screen::move(pos row, pos col) {
	pos rows = row * width;
	cursor = rows + col;
	return *this;
}
inline Screen &Screen::set(char c) {
	contents[cursor] = c;
	return *this;
}
inline Screen &Screen::set(pos row, pos col, char c) {
	contents[row * width + col] = c;
	return *this;
}

練習7.28

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

Screen對象將cursor移動到了20的位置,後續的操作都將與新返回的副本進行。’#'被賦值到了副本中。第一個display展示的是被賦值過有的副本。第二個display展示的是全X。

練習7.29

  • 修改你的Screen 類,令move、set和display函數返回Screen並檢查程序的運行結果,在上一個練習中你的推測正確嗎?

    正確

練習7.30

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

通過this指針訪問成員的優點是可以非常明確地指出訪問的是對象的成員,並且可以在成員函數中使用與數據成員同名的形參;缺點是顯得多餘,代碼不夠簡潔。

7.3.3節練習

練習7.31

  • 定義一對類X和Y,其中X 包含一個指向 Y 的指針,而Y包含一個類型爲 X 的對象。
int main()
	{
	class Y;
	class X {
		Y *py;
	};
	class Y {
		X x;
	};
	}a

7.3.4節練習

練習7.32

  • 定義你自己的Screen 和 Window_mgr,其中clear是Window_mgr的成員,是Screen的友元。
#include <string>
using namespace std;
class Screen;    //Windows_mgr含有Screen對象,因此得先聲明
class Window_mgr {  //先定義Window_mgr類
public:
	using ScreenIndex = vector<Screen>::size_type;    //Screen座標
	void clear(ScreenIndex i);  //聲明函數,但不定義
private:
	vector<Screen> screens;//{Screen(24, 80, ' ')};直接調用Screen的構造函數將會報錯
};
class Screen {  //後聲明並定義Screen類
public:
	friend void Window_mgr::clear(ScreenIndex i);   //並聲明友元函數
	typedef string::size_type pos;
	Screen() = default;
	Screen(pos ht, pos wd) : height(ht), width(wd), contents(ht * wd, ' ') { };
	Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) { };
    //...
private:
	pos cursor = 0;
	pos height = 0, width = 0;
	string contents;
};
//...
inline void Window_mgr::clear(ScreenIndex i) {  //最後定義clear函數
	Screen &s = screens[i];
	s.contents = string(s.height * s.width, ' ');
}

7.4節練習

練習7.33

  • 如果我們給Screen 添加一個如下所示的size成員將發生什麼情況?如果出現了問題,請嘗試修改它。
pos Screen::size() const
{
    return height * width;
}
//返回類型錯誤,在Screen類型外沒有pos這樣的數據類型。數據類型將未知。應改爲:
Screen::pos Screen::size() const
//...

7.4.1節練習

練習7.34

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

pos將是未定義的標誌符,導致編譯失敗。

練習7.35

  • 解釋下面代碼的含義,說明其中的Type和initVal分別使用了哪個定義。如果代碼存在錯誤,嘗試修改它。
typedef string Type;    //定義Type類型爲string
Type initVal();    // 全局函數聲明,返回類型爲string
class Exercise
{
public:
    typedef double Type;    //定義Type類型爲double
    Type setVal(Type);    //成員函數,返回類型,形參爲double
    Type initVal();       //成員函數,返回類型爲double(隱藏了同名的函數)
private:
    int val;
};

Type Exercise::setVal(Type parm)    //成員函數的定義,返回類型爲string 參數類型爲double
{
    val = parm + initVal();    //成員函數爲initVal()
    return val;
}
//setVal函數返回值的類型和返回類型不符,改爲:
Exercise::Type Exercise::setVal(Type parm)

7.5.1節練習

練習7.36

  • 下面的初始值是錯誤的,請找出問題所在並嘗試修改它。
struct X{
    X (int i, int j): base(i), rem(base % j) {}
    int rem, base;
};
根據類定義內出現的順序,rem將被優先初始化,而此時base未被初始化,因此錯誤。改爲:
    int base, rem;

練習7.37

  • 使用本節提供的Sales_data類,確定初始化下面的變量時分別使用了哪個構造函數,然後羅列出每個對象所有的數據成員的值。
Sales_data first_item(cin);    // Salas_data(std::istream &is) 用戶輸入的值
int main() {
    Sales_data next;    // Sales_data(std::string s = "")  bookNo="",cnt=0,rev=0.0
    // Sales_data(std::string s = "") bookNo="9-999-99999-9",cnt=0,rev=0.0
    Sales_data last("9-999-99999-9");
}

練習7.38

  • 有些情況下我們希望提供cin作爲接受istream& 參數的構造函數的默認實參,請聲明這樣的構造函數。
Sales_data(std::istream &is = std::cin) { read(is, *this)}
//該構造函數已有了默認構造函數的功能,爲避免二義性,因將原構造函數刪除

練習7.39

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

不合法。此時使用構造函數且不輸入參數,則兩個構造函數都可以爲其進行默認構造,將無法選擇使用哪個函數,引起二義性。

練習7.40

  • 從下面的抽象概念中選擇一個(或者你自己指定一個 ,思考這樣的類需要哪些數據成員, 提供一組合理的構造函數並闡明這樣做的原因。
(a) Book           (b) Data           (c) Employee
(d) Vehicle        (e) Object        (f) Tree
class Tree
{
private:
	std::string Name;
	unsigned Age = 0;
	double Height = 0;
public:
	Tree() = default;
	Tree(const std::string &n, unsigned a, double h)
		: Name(n), Age(a), Height(h)
};

7.5.2節練習

練習7.41

  • 使用委託構造函數重新編寫你的Sales_data 類,給每個構造函數體添加一條語句,令其一旦執行就打印一條信息。用各種可能的方式分別創建Sales_data對象,認真研究每次輸出的信息直到你確實理解了委託構造函數的執行順序。
#include <iostream>
#include <string>
using namespace std;
class Sales_data;
istream &read(istream &is, Sales_data &item);
class Sales_data {
private:
	std::string bookNo;
	unsigned units_sold = 0;
	double revenue = 0.0;
public:
	Sales_data(const string &s, unsigned n, double d) :bookNo(s), units_sold(n), revenue(n*d) { cout << "3 parameter" << endl; }
	Sales_data() : Sales_data("", 0, 0.0) { cout << "0 parameter" << endl; }
	Sales_data(const string &s) : Sales_data(s, 0, 0.0) { cout << "1 parameter" << endl; }
	Sales_data(istream &is):Sales_data() { read(is, *this); cout << "istream parameter" << endl; }
    friend istream &read(istream &is, Sales_data &item);
};
istream &read(istream &is, Sales_data &item) {
	double price = 0;
	is >> item.bookNo >> item.units_sold >> price;
	item.revenue = item.units_sold * price;
	return is;
}
int main(int argc, char const * argv[])
{
	Sales_data s1;
	Sales_data s2("cin");
    Sales_data s3("cin", 5, 8);
    return 0;
}

將會優先調用被委託的構造函數,及其函數體,再返回至調用函數的函數體。

練習7.42

  • 對於你在練習7.40 (參見7.5.1節,第261 頁)中編寫的類,確定哪些構造函數可以使用委託。如果可以的話,編寫委託構造函數。如果不可以,從抽象概念列表中重新選擇一個你認爲可以使用委託構造函數的,爲挑選出的這個概念編寫類定義。
class Book
{
private:
	string Name, ISBN, Author, Publisher;
	double Price = 0;
public:
	Book(const string & n, const string &I, double pr, const string & a, const string & p)
		: Name(n), ISBN(I), Price(pr), Author(a), Publisher(p) { }
	Book() : Book("", "", 0, "", "") { }
	Book(std::istream & is) : Book() { is >> *this; }
};

7.5.3節練習

練習7.43

  • 假定有一個名爲NoDefault 的類,它有一個接受int的構造函數,但是沒有默認構造函數。定義類C, C 有一個NoDefault 類型的成員,定義C 的默認構造函數。
using namespace std;
class NoDefault {
public:
	NoDefault(int i) : val(i) {};
private:
	int val;
};
class C {
public:
	C(int i = 0) : A(i) {}
private:
	NoDefault A;
};
int main(){
	C();
}

練習7.44

  • 下面這條聲明合法嗎?如果不,爲什麼?
vector<NoDefault> vec(10);

這條語句的意思是初始化一個vector,其中包含10個NoDefault類(由其自身默認初始化)。而該類型不存在默認構造函數,無法成功構造因此不合法。

練習7.45

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

合法,C可以默認初始化。

練習7.46

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

(a)錯誤。見(d)

(b)錯誤。默認構造函數也可以有參數,爲對應的成員提供默認初始化。

(c)錯誤。一般情況下,類需要一個默認構造函數來初始化其成員。

(d)錯誤。只要類內有了構造函數,編譯器就不會再爲其生成構造函數。

7.5.4節練習

練習7.47

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

應該是explicit的,這樣可以防止編譯器自動的把一個string對象轉換成Sales_data對象,這樣可能會導致意想不到的後果。

使用explicit的優點是避免因隱式轉換而帶來的意想不到的錯誤,缺點是用戶的確需要這樣的類類型轉換時,不得不使用略顯繁瑣的方式來實現。

練習7.48

  • 假定Sales_data 的構造函數不是explicit 的,則下邊定義將執行什麼樣的操作?如果Sales_data 的構造函數是explicit 的,又會發生什麼呢?
string null_isbn("9-999-99999-9");
Sales_data item1(null_isbn);
Sales_data item2("9-999-99999-9");
不是explicit。
Sales_data item1(null_isbn);//定義了一個Sales_data對象,該對象利用null_isbn轉換得到的臨時對象通過構造函數進行初始化
Sales_data item2("9-999-99999-9");//定義了一個Sales_data對象,該對象使用字符串字面值轉換得到的臨時對象通過構造函數進行初始化
是explicit同上。

練習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)編譯無法通過。因爲我們把combine成員函數聲明成了常量成員函數,所以該函數無法修改數據成員的值。

練習7.50

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

將構造函數Person(std::istream & is)定義爲explicit。

練習7.51

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

如果vector單參數構造函數不是explicit的,那麼對於這樣的一個函數void fun(vector v)來說,可以直接以這樣的形式進行調用fun(5),這種調用容易引起歧義,無法得知實參5指的是vector的元素個數還是隻有一個值爲5的元素。而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。

略。

練習7.54

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

不能。constexpr函數有且只能包含一條return語句。

練習7.55

  • 7.5.5節(第266頁)的 Data 類是字面值常量類嗎?請解釋原因。

不是。s的數據類型string不是字面值類型。數據類型都是字面值類型的聚合類是字面值常量類。

7.6節練習

練習7.56

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

靜態成員是指聲明語句之前帶有關鍵字static的類成員,靜態成員不是任意單獨對象的組成部分,而是由該類的全體對象所共享。

靜態成員的優點包括:作用域位於類的範圍之內,避免與其他類的成員或者全局作用域的名字衝突;可以是私有成員,而全局對象不可以;通過閱讀程序可以非常容易地看出靜態成員與特定類關聯,使得程序的含義清晰明瞭。

靜態成員與普通成員的區別主要體現在普通成員與類的對象關聯,是某個具體對象的組成部分;而靜態成員不從屬於任何具體的對象,它由該類的所有對象共享。另外,還有一個細微的區別,靜態成員可以作爲默認參數,而普通成員不能作爲默認參數。

練習7.57

  • 編寫你自己的Account 類。
class Account
{
private:
	string strName;
	double dAmount = 0.0;
	static double dRate;
};

練習7.57

  • 下面的靜態數據成員的聲明和定義有錯誤嗎?請解釋原因。
//example.h
class Example{
public:
    static double rate = 6.5;     //不是字面值常量類型的常量表達式的靜態數據成員不允許在類內初始化
    //正確,但是在類外應該在定義一下,比如:const int Example::vetSize
    static const int vecSize = 20;
    //錯誤,constexpr static數據成員必須是字面值類型,vector非字面值類型,不允許類內初始化
    static vector<double> vec(vecSize);
};
//example.C
#include “example.h”           //兩者在上面都錯了,需要重新給出初始值
double Example::rate;
vector<double> Example::vec;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章