C++自修入門實境秀、C++ Primer 5版研讀秀 77/ ~ v12動態記憶體(dynamic memory)12.1.1. The s...





Element Access Members

元素存取成員

存取元素的成員函式

8:33:50

void pop_back();//pop_back//只有宣告,沒有實作(定義)

// element access

std::string &front();

std::string &back();



void check(size_type i, const std::string &msg) const; //常值的const成員函式,只有宣告

Defining the StrBlob Class

18:41

check takes a string argument that it will pass to the exception handler. The string describes what went wrong:

void StrBlob::check(size_type i, const string &msg) const

{

if (i >= data->size())

throw out_of_range(msg);

}

23:00

string &StrBlob::front()

{

// if the vector is empty, check will throw

check(0, "front on empty StrBlob");

return data->front();

}

string &StrBlob::back()

{

check(0, "back on empty StrBlob");

return data->back();

}

void StrBlob::pop_back()

{

check(0, "pop_back on empty StrBlob");

data->pop_back();

}

27:00

The front and back members should be overloaded on const (§ 7.3.2, p. 276).Defining those versions is left as an exercise.

front與back成員應該重載於const之上(§ 7.3.2,頁276)。這些版本的定義就留作練習。

應該重載於const之上→這是人話嗎?應該要做一個front和back的const版本(的重載)



在const上重載front與back這兩個成員函式

Copying, Assigning, and Destroying StrBlobs

Like our Sales_data class, StrBlob uses the default versions of the operations that copy, assign, and destroy objects of its type (§ 7.1.5, p. 267).

對一個shared_ptr做指定assign,則其參考計數(reference count)左運算元會遞減,而右運算元者會遞增。

當沒有任何的StrBlob物件指向vector時,這個由StrBlob類別建構的vector就會被註銷、銷毀,而釋放記憶體

頁458

練習12.1

1:19:00

b1 has four and b2 is destroyed

標頭檔 StrBlob.h:

#ifndef STRBLOB_H

#define STRBLOB_H

#include<string>

#include<memory>

#include<vector>

using namespace std;

class StrBlob

{

public:

typedef vector<string>::size_type size_type;//以型別別名定義型別成員(type member)

StrBlob();//預設建構器,只有宣告,沒有定義(實作)

StrBlob(initializer_list<string> il);//帶了一個initializer_list<string>參數的建構器,也只有宣告,沒有定義(實作)

size_type size() const { return data->size(); }//常值的const成員函式

bool empty() const { return data->empty(); }//常值的const成員函式

// add and remove elements

void push_back(const string &t) { data -> push_back(t); }

void pop_back();//pop_back//只有宣告,沒有實作(定義)

// element access

string &front();

string &back();



private:

shared_ptr<vector<string>> data;

// throws msg if data[i] isn't valid

void check(size_type i, const string &msg) const; //常值的const成員函式,只有宣告

};

#endif



源碼檔(source file) StrBlob.cpp:

#include "StrBlob.h"

#include<memory>//make_shared 要用

#include <stdexcept>//out_of_range 要用

using namespace std;

//StrBlob::StrBlob() : data(make_shared<vector<string>>()) {}

StrBlob::StrBlob()

{

data = make_shared<vector<string>>();

}

//StrBlob::StrBlob(initializer_list<string> il) : data(make_shared<vector<string>>(il)) {}//用il來作為make_shared引數,就不是空的vector了

StrBlob::StrBlob(initializer_list<string> il)

{

data = make_shared<vector<string>>(il);

}



void StrBlob::check(size_type i, const string& msg) const

{

if (i>=data->size())

{

throw out_of_range(msg);//須引用stdexcept標頭 #include <stdexcept>

}

}



void StrBlob::pop_back()

{

check(0, "pop_back on an empty StrBlob");

data->pop_back();

}



string& StrBlob::front()

{

check(0, "front on an empty StrBlob");

return data->front();

}



string& StrBlob::back()

{

check(0, "back() on an empty StrBlob");

return data->back();

}



執行檔 prog1.cpp:

#include<iostream>

#include"StrBlob.h"

using namespace std;

int main() {

StrBlob b1;

{//可以直接加大括號來畫定範疇

StrBlob b2 = { "a", "an", "the" };

b1 = b2;

b2.push_back("about");

}

cout << b1.size() << endl;

}



練習12.2

2:34:00

複習前面Overloading Based on const(頁276-277)

// element access

string &front();

const string &front()const;

string &back();

const string &back()const;





string& StrBlob::front()

{

check(0, "front on an empty StrBlob");

return data->front();

}



const string& StrBlob::front() const

{

check(0, "front on an empty StrBlob");

return data->front();

}



string& StrBlob::back()

{

check(0, "back() on an empty StrBlob");

return data->back();

}



const string& StrBlob::back() const

{

check(0, "back on an empty StrBlob");

return data->back();

}



StrBlob b1;



cout << b1.size() << endl;

const StrBlob cB1({ "a", "an", "the" });

cout << b1.back() << endl;

cout << cB1.size() << endl;

cout << cB1.back() << endl;

作用在常值的物件才會調用常值的版本

全部程式碼詳https://github.com/oscarsun72/prog1-C-Primer-5th-Edition-s-Exercises/tree/exercise12_02

練習12.3

2:53:50

const版本要在常值const的物件上才會被調用,既然是唯讀const的,如何push_back()、pop_back()。就好像前面Screen的例子,set只能在非常值nonconst上調用一樣。只有在不需要更動原物件下,才會考慮用const版本:

邏輯上來說,顯示(display)一個Screen並不會改變該物件,所以我們應該讓display成為一個const成員。(Returning *this from a const Member Function)

練習12.4

因為 i的型別是size_type 必然是大於等於0的整數

練習12.5

3:3:05 3:6:20

複習前面7.5.3. The Role of the Default Constructor

5:49:00 詳實境秀講解

利(優點):大概就是可以進行隱含轉型,不必具名,就可以直接用initializer_list引數來建構一個StrBlob物件來進行操作運算,少寫一些程式碼的

在一個用到StrBlob物件的地方,可以只提供這個引數即可









7.3.2. Functions That Return *this

7.3.2回傳*this的函式 第77集1:43:30

函式只有回傳參考(reference)才是左值(lvalue)

2:33:00

Functions that return a reference are lvalues (§ 6.3.2, p. 226), which means that they return the object itself, not a copy of the object. If we concatenate a sequence of these actions into a single expression:

// move the cursor to a given position, and set that character

myScreen.move(4,0).set('#');

⑧找對主詞

Had we defined move and set to return Screen, rather than Screen&, this statement would execute quite differently. In this case it would be equivalent to:

頁276

// if move returns Screen not Screen&

Screen temp = myScreen.move(4,0); // the return value would be copied

temp.set('#'); // the contents inside myScreen would be unchanged



第77集1:46:48 1:52:07

2:42:37

Returning *this from a const Member Function

從一個const成員函式回傳*this

第77集1:54:27

2:55:00

邏輯上來說,顯示(display)一個Screen並不會改變該物件,所以我們應該讓display成為一個const成員。

不想改變成所指的對象,參考就應當是對一個常值const——或者說——唯讀的參考。C++中似沒有唯讀的概念,只有const,就是此義。

第77集2:6:17

pdf版有誤,中文版也未訂正,可見是據此而譯出的

Hence, the return type of display must be const Sales_data&.

因此,display的回傳型別必須是const Sales_data&。

google圖書版已訂正:

Hence, the return type of display must be const Screen& .



Note: A const member function that returns *this as a reference should have a return type that is a reference to const .

⑧找對主詞 const成員函式時對*this的const

Overloading Based on const

基於const的重載

我們可以依據它是否為const來重載一個成員函式,就像我們可以依據一個指標參數是否指向const來重載一個函式一樣(§ 6.4)。





頁277

3:32:00

第77集 2:31:00

當我們在一個物件上呼叫display,那個物件是否為const決定了哪個版本的display會被呼叫:

Screen myScreen(5, 3);

const Screen blank(5, 3);

myScreen.set('#').display(cout); // 呼叫非 const 版本

blank.display(cout); // 呼叫 const 版本



Advice: Use Private Utility Functions for Common Code

建議:為共通的程式碼使用私有的工具函式(PRIVATE UTILITY FUNCTIONS)

如果有通用性質的函式,就盡量使用private機制封裝起來

3:43:00 不見題目只見關鍵字 one:

Some readers might be surprised that we bothered to define a separate do_display operation. After all, the calls to do_display aren’t much simpler than the action done inside do_display. Why bother? 幹嘛如此煞費苦心? 第77集2:25:00

We do so for several reasons:





Classes must have a default constructor in order to be used in these contexts.

當一個類別中的成員,其型別是沒有預設建構器時,我們若不提供該成員適當的初始器,則會發生錯誤

class NoDefault

{

public:

NoDefault(const std::strings);//守真按:非預設建構器

//接下來是額外的成員,但沒有其他的建構器

};



struct A

{ // my_mem 預設是 pobiic 的,參閱 § 7.2

NoDefault my_mem;

};

A a; //錯誤:無法為A合成一個建構器;守真按:因為NoDefault沒有預設建構器

所以無法隱含地讓my_mem這個NoDefault型別的物件被預設初始化



struct B

{

B() {} //錯誤:b_member沒有初始器

NoDefault b_member;//守真按:這裡的錯誤是預設建構器未將資料成員初始化(未提供資料成員的初始器)

};



Best Practices:養成好習慣

In practice, it is almost always right 最好to provide a default constructor if other constructors are being defined.

實務上,如果有定義其他的建構器,那提供一個預設建構器幾乎永遠是正確的選擇。

只要定義了其他建構器,就請務必也定義預設建構器 感恩感恩 南無阿彌陀佛 第77集 3:27:00

1:53:00 always right應當翻為「最好」「almost always right」:絕對是最好的,或者說「可以確定是最好的」

Using the Default Constructor



頁294

編譯器會抱怨說我們無法將成員存取記號套用到一個函式上。

這是哪家的中文?抱怨,可以用在無生命、意識的編譯器上頭嗎?也太擬人化了吧,又不是在作文!第77集3:34:00 用「說」還可以,用抱怨、還抱怨說,就太過分了。禮節禮節,沒有節,豈不就無禮了!

2:3:00 2:8:50

定義使用預設建構器來初始化的一個物件的正確方式是移除那個尾隨的空括弧

未加空括號就是要用預設建構器。預設建構器就是不帶引數的建構函式第77集3:37:00





7.5.4. Implicit Class-Type Conversions

隱含的類別型別轉型

自動轉型

默認轉型

預設轉型

第77集3:38:40





類別型別的隱含轉型——用單一引數調用的建構器定義了這樣的轉型:

We also noted that classes can define implicit conversions as well. Every constructor that can be called with a single argument defines an implicit conversion to a class type.

也就是單一引數的型別可以被隱含轉型為類別型別

能以單一個引數呼叫的每個建構器都定義了一種轉向(to)某個類別型別的隱含轉換

Such constructors are sometimes referred to as converting constructors .

轉換建構器(converting constructors)

這樣的建構器就被看作是轉換建構器

我們會 在§ 14.9(頁579)看到如何定義從一個類別型別轉為另一個的轉換。

第77集3:47:00

頁295

Note: A constructor that can be called with a single argument defines an implicit conversion from the constructor’s parameter type to the class type.

從建構器的參數型別,轉成其類別型別

也就是編譯器會默認地把符合單一參數(引數)的建構器其引數的型別的物件當作這個建構器的引數來傳遞,因之就建構了該類別型別的物件。如:

item.combine(null_book);

明明()中的要Sales_data類別型別,可是因為傳了一個與其建構器的引數數量及型別相符的引數(這裡null_book是string型別,與Sales_data其中一個建構器能「匹配」match),所以編譯器就會自動轉譯為用該建構器來建構一個Sales_data類別型別的物件:

Here we call the Sales_data combine member function with a string argument. This call is perfectly legal; the compiler automatically creates a Sales_data object from the given string . That newly generated (temporary) Sales_data is passed to combine .

4:24:30第77集 3:51:30

Only One Class-Type Conversion Is Allowed

隱含轉型(implicit)一次只能一個,而明確轉型(explicit)就不限了

只允許一個類別型別的轉換

字串字面值(string literal)或character string(字元字串)並非string型別!

因此,這樣是可以的:

string null_book = "9-999-99999-9";(這裡其實就已經隱含轉型一次了)

// constructs a temporary Sales_data object

// with units_sold and revenue equal to 0 and bookNo equal to null_book

item.combine(null_book);

這樣就不行:

// error: requires two user-defined conversions:

// (1) convert "9-999-99999-9" to string

// (2) convert that (temporary) string to Sales_data

item.combine("9-999-99999-9");

If we wanted to make this call, we can do so by explicitly converting the character string to either a string or a Sales_data object:

// ok: explicit conversion to string, implicit conversion to Sales_data 隱含的轉型只能一次,不能連續;只能單擊,不能連發

item.combine(string("9-999-99999-9"));

// ok: implicit conversion to string, explicit conversion to Sales_data

item.combine(Sales_data("9-999-99999-9"));

可見字元字串或字串字面值與string有隱含轉型的關係

其實「string("9-999-99999-9")」「Sales_data("9-999-99999-9")」都是單一引數建構器的概念

單一引數自然就是單一型別。4:30:55

可見隱含轉型都是建構器在搞鬼;露出建構器的面目,就是明確轉型;沒露出,就是隱含;而建構器名稱就與類別名稱相同;所以一看到類別名稱出現,只要是單一引數,就知道是建構器轉型要出現了。誰出現類別名稱(即建構器名稱)誰就在做明確轉型。這裡的所謂明確轉型就是要轉成的類別名稱有沒有跑出來的意思;和前面討論明確轉型時要出現轉型的關鍵字cast等是不一樣的。第77集 4:8:00

Class-Type Conversions Are Not Always Useful

類別型別的轉型並不總是管用、用得著、用得到

類別型別轉換並不一定總是有用

4:35:00

頁296

第77集5:5:00

這段程式碼會隱含地將cin轉為Sales_data。這個轉換會執行接受一個istream的那個 Sales_data建構器。那個建構器會藉由讀取標準輸入來創建一個(暫存的)Sales_data物件。

所以所謂的一個參數的建構器所定義的隱含轉型,其實仍只是呼叫那個建構器來建構一個型別物件罷了,並不是真的所謂的轉型;或者所謂的「轉型」(鑄型(cast))的意義,其實仍只是調用那個要被轉到的型別的建構器罷了





Suppressing Implicit Conversions Defined by Constructors

抑制建構器所定義的隱含轉換

讓建構器定義的隱含轉型失效(disable)不可用。

We can prevent the use of a constructor in a context that requires an implicit conversion by declaring the constructor as explicit:

我們可以在需要隱含轉換的情境中避免建構器的使用,方法是將建構器宣告為explicit :

這裡的requires應該是翻成「會」或「可能」才對。如果是「需要」,那為什麼不直接就用建構器來隱含轉型就好。如「requirement」即必要條件,可見字根「require」亦應有「必然」「會」的意思在內

情境也是用詞不當,「情況」就可以了。情境通常是和人(有感情、覺知)的有關的

class Sales_data

{

public:

Sales_data() = default;

Sales_data(const std::string &s, unsigned n, double p) : bookNo(s), units_sold(n), revenue(p * n) {}

explicit Sales_data(const std::string &s) : bookNo(s) {}

explicit Sales_data(std::istream &);

//剩餘的成員一如以往

} ;



declaring the constructor as explicit :

將建構器宣告為「explicit」

explicit Sales_data(const std::string &s): bookNo(s) { }

explicit Sales_data(std::istream&);

對單一參數的建構器才需如此:

The explicit keyword is meaningful only on constructors that can be called with a single argument.

Now這樣一來, neither constructor can be used to implicitly create a Sales_data object.

neither沒有一個,表示不管是帶string或istream參數的建構器都不能再隱含地做轉型了

可見其實是隱含地藉由符合引數型別的變數來建構了一個Sales_data的物件,並不是將該變數由其型別轉型為Sales_data類別型別

其實是建構,而不是轉型!

對需要多引數的建構器而言,它本身隱含地就是explicit,就不需要指明designate它是explicit

The explicit keyword is used only on the constructor declaration inside the class. It is not repeated on a definition made outside the class body:

explicit只冠在建構器在類別內的宣告中,不會在類別外的建構器定義中

// error: explicit allowed only on a constructor declaration in a class header

explicit Sales_data::Sales_data(istream& is)

{

read(is, *this);

}

4:57:20

explicit Constructors Can Be Used Only for Direct Initialization

explicit建構器只能用於直接初始化

explicit建構器只能用作直接地初始化

5:5:00

還有一種情況會發生隱含的類別型別的轉型,就是在做拷貝形式的初始化時:

One context in which implicit conversions happen is when we use the copy form of initialization (with an = ) (§ 3.2.1 , p. 84 ). We cannot use an explicit constructor with this form of initialization; we must use direct initialization:

可見=和()的初始化還是有所不同的:

Sales_data item1 (null_book); // ok: direct initialization

// error: cannot use the copy form of initialization with an explicit constructor

Sales_data item2 = null_book;

()才是直接初始化,而=是拷貝初始化

使用=初始化一個變數時,我們是在請求編譯器將右手邊的初始器拷貝到建立出來的物件,藉此copy initialize (拷貝初始化)那個物件。否則的話,如果我們省略那個=,我們用的就是direct initialization (直接初始化)。(頁84)

可見所謂的「直接」就是不用「=」的意思!

可見拷貝初始化也就是間接初始化。

頁297

Explicitly Using Constructors for Conversions

第77集 4:33:40前面提過只要出現建構器名稱(即類別名稱)就是明確地在用建構器作轉型了

為轉換明確地使用建構器

Although the compiler will not use an explicit constructor for an implicit conversion, we can use such constructors explicitly to force a conversion:

所以並不是宣告為explicit的建構器,就不能執行型別轉換了;只是編譯器不做罷了,我們仍然可以自己做

明確地使用該建構器也可以說是「具名」地使用:

// ok: the argument is an explicitly constructed Sales_data object

item.combine(Sales_data(null_book));

// ok: static_cast can use an explicit constructor

item.combine(static_cast<Sales_data>(cin));

5:31:30 5:34:10第77集4:53:40具名、明確就如不遮遮掩掩、沒頭沒臉,能夠正大光明、有名有姓、具名負責,就如此例:https://youtu.be/WUg28AJfwZU

第77集5:31:00 什麼叫「static_cast can use an explicit constructor」。由此亦可見,原來cast(鑄型(cast)、轉型)仍舊是藉助類別的建構器才能進行所謂的「轉型」的嘛!所以所謂的「轉型」static_cast<Sales_data>(cin),其實就是static_cast調用了Sales_data類別的帶著cin型別(i.e.istream)的引數的建構器來建構一個要轉型到的型別嘛

原來「轉型」就是「建構」!轉型到的型別就是轉型運算要建構的型別。

在第二個呼叫中,我們使用一個static_cast(§4.11.3)來進行一種明確的轉換,而非隱含的。在這個呼叫中,static_cast使用istream建構器來建構一個暫存的Sales_data物件。

Library Classes with explicit Constructors

具有explicit建構器的程式庫類別



練習7.49

//(a)Sales_data& combine(Sales_data);

//(b)Sales_data& combine(Sales_data&);//只有這個不行!

//(c)Sales_data& combine(const Sales_data&) const;//要拿掉尾巴的const才行

或者要這樣:const Sales_data& combine(const Sales_data&) const;//第77集4:48:00



練習7.51

Why do you think vector defines its single-argument constructor as explicit, but string does not?

你認為為什麼…… 第77集4:39:00 ⑧找對主詞 什麼東東why啊?why的主人是vector、string,不是 you!explicit也是要⑧找對主詞 是指轉型的explicit (轉型要明渡,不能偷渡),不是別的。

中文版翻錯成「為什麼你會認為……」

如果把size_type轉型成vector會成什麼樣子!

6:54:59第77集5:41:50 這個size_type又不是vector的元素型別,而vector是模板型別(template type),在定義(建構)時一定要明確指定元素的型別,和陣列一樣,元素的型別是vector型別本身的一部分。如果只傳size_type就要隱含轉型,編譯器怎麼知道您要用的元素是什麼型別呢?

留言

熱門文章