C++自修入門實境秀、C++ Primer 5版研讀秀 65/ ~ v10 泛用演算法(generic algorithms)10.4.2. ...



Arguments to bind

bind的引數

bind函式的引數(又囿於字典字義文法結構了)

⑦先抓動詞 此bind是函式名,不是動詞!

引數arguments是用介係詞to



6:35

As we’ve seen, we can use bind to fix the value of a parameter.

如我們所見,我們可以使用bind來固定一個參數的值。

這就是函式轉接器(adaptor)適配器(adaptor)的用途

調適,前例只是「固定」,而:

更廣義的說,我們可以使用bind來繫結或重新安排所給的callable中的參數。

More generally, we can use bind to bind or rearrange the parameters in the given callable.

繫結或重新安排就是調適、調整

15:30

// g is a callable object that takes two arguments

auto g = bind(f, a, b, _2, c, _1);

預留位置(placeholder)數即bind回傳的可呼叫物件(callable object)的引數數

預留位置(placeholder)必定有「_n」這樣的格式

generates a new callable that takes two arguments, represented by the placeholders _2 and _1 .

會產生一個新的callable,它接受兩個引數,由預留位置_2和_1代表。

The new callable will pass its own arguments as the third and fifth arguments to f.

這個新的callable 會將它的引數當作第三和第四引數傳入給f。

第四→第五

可見預留位置就是new callable的引數

而bind()後綴的參數列是照冠首的callable的參數列來排序的

bind(f, a, b, _2, c, _1)

因此 _2、_1,這兩個new callable的引數就被當作f的第3個與第5個參數/引數來傳入f

43:00

The arguments to g are bound positionally to the placeholders. That is, the first argument to g is bound to _1 , and the second argument is bound to _2 . Thus, when we call g , the first argument to g will be passed as the last argument to f ; the second argument to g will be passed as f ’s third argument. In effect, this call to bind maps ⑦先抓動詞 相當於(因為地圖就相當於實際的地理情形)

g(_1, _2)

to

f(a, b, _2, c, _1)

在效果上,對bind的這個呼叫會將 g(_1,_2)

映射到

f(a, b, _2, c, _1)

That is, calling g calls f using g ’s arguments for the placeholders along with the bound arguments, a , b , and c . For example, calling g(X, Y) calls f(a, b, Y, c, X)

49:00

頁400

Using to bind to Reorder Parameters

這個bind應是動詞(不定詞)

懷疑當作「Using the bind to Reorder Parameters」或「Using bind to Reorder Parameters」

使用bind來重新排列參數

使用bind這個函式來重排參數

isShorter在頁386

59:00

Binding Reference Parameters

bind繫結就是手機號碼常說的「綁定」



預設的情況下,bind非預留位置(placeholder)引數的傳遞是用拷貝的方式

1:7:00

ostream &print(ostream &os, const string &s, char c)

{

return os << s << c;

}



// error: cannot copy os

for_each(words.begin(), words.end(), bind(print, os, _1, ' '));

ostream無法被拷貝,但string可以

If we want to pass an object to bind without copying it, we must use the library ref function:

ref、cref函式

for_each(words.begin(), words.end(),

bind(print, ref(os), _1, ' '));

函式就要用呼叫運算子(call operator)

The ref function returns an object that contains the given reference and that is itself copyable.

ref會回傳一個物件,它含有給定的參考,而且本身是可以拷貝的。

There is also a cref function that generates a class that holds a reference to const .

Like bind , the ref and cref functions are defined in the functional header.

這都是跟函式的使用有關,所以都定義在functional標頭檔

1:15:40

頁401

Backward Compatibility: Binding Arguments

回溯相容性(backward compatibility):繫結引數

向下相容(與舊版本相容)

Older versions of C++ provided 有著 中文不能翻成「提供」!表哥原理 會「提供」,不就「擁有」了嗎?不就「有」了嗎?要用「提供」翻譯,就須加「只」,「只提供了」,才符合上下文意。 a much more limited, yet more complicated,set of facilities to 去bind arguments to functions. 綁定引數給函式(用)

可見「Binding Arguments」就是⑧找對主詞 將引數與函式繫結,或者將函式的引數與某一個可呼叫物件(callable object)繫結。引數跟誰繫結?和函式或可呼叫物件(callable object)也。

The library defined two functions named bind1st and bind2nd. Like bind, these functions take a function and generate a new callable object that calls the given function with one of its 應是代「a new callable」parameters bound to a given value. However, these functions can bind only the first or second parameter, respectively.

所以才叫「bind1st and bind2nd.」

Because they are of much more limited utility,

原來 utility的字根是use喔

they have been deprecated 過時的 in the new standard. A deprecated feature is one that may not be supported in future releases.Modern C++ programs should use bind.

1:35:35

練習10.22

1:43:10

#include <iostream>

#include <string>

#include <vector>

#include <algorithm>

#include <functional>

using namespace std;

using namespace std::placeholders;//若沒此行,則「_1」就undefined

bool isShorterAndEqualSz(const string& s, string::size_type sz) {

return s.size() <= sz;

}

int main() {

vector<string>v;

string w;

unsigned sz=6;

while (cin >> w)

v.push_back(w);

auto b = bind(isShorterAndEqualSz, _1, sz);

cout<<count_if(v.cbegin(), v.cend(), b)<<endl;//此3種均可以,此題不用lambda

//可見在呼叫這個b (new callable 可呼叫物件 callable object)不用加呼叫運算子(call operator),也不必傳遞引數

//cout<<count_if(v.cbegin(), v.cend(), bind(isShorter, _1, sz))<<endl;

//cout<<count_if(v.cbegin(), v.cend(), [](const string& s)->bool {return s.size() <= 6; })<<endl;



}

練習10.23

1:37:00

bind接受多少引數?

理論上不限啊,是看它第一個函式引數帶了多少「拖油瓶」引數/參數過來而決定的啊 呵呵

不過也因此可見bind此種函式是如何特別的了,參數列竟然是活的、可變的。

參數列中有「...」,放在最後面

Ellipsis Parameters

6.2.6. Functions with Varying Parameters

2:28:08

練習10.24

#include <iostream>

#include <string>

#include <vector>

#include <functional>

#include <algorithm>

using namespace std;

using namespace std::placeholders;

bool check_size(const string& s, string::size_type sz) {

return s.size() >= sz;

}

int main() {

vector<int>v{ 10,12,-3,14,3,2,4 };//int型別和size_type做運算時有風險,應先過濾無負值才能進行

sort(v.begin(), v.end());

v.erase(stable_partition(v.begin(), v.end(), [](const int& i)->bool {return i >= 0; }));

//若無以上2行(erase v中小於0的值),print(the answer is): -3

string w = "孫守真任真甫";

cout << *find_if_not(v.cbegin(), v.cend(),

bind(check_size, w, _1)) << endl;//print(the answer is): 14

}

2:57:20 3:6:00

練習10.25

練習10.18

#include <iostream>

#include <string>

#include <vector>

#include <algorithm>

#include <functional>

using namespace std::placeholders;

using namespace std;

bool check_size(const string& s, string::size_type sz) {

return s.size() >= sz;

}

void biggies(vector<string>& vecStr, vector<string>::size_type sz) {

auto iterE = partition(vecStr.begin(), vecStr.end(),//先分組再排序。若先排序再分組則會亂序

bind(check_size,_1,sz));

sort(vecStr.begin(), iterE);

stable_sort(vecStr.begin(), iterE

, [](const string& s1, const string& s2) {return s1.size() < s2.size(); });

for_each(vecStr.begin(), iterE, [](const string& s) {cout << s << endl; });

}

int main() {

vector<string> vec;

string word;

while (cin >> word)

{

vec.push_back(word);

}

biggies(vec, 8);

}



3:27:00學習心得:

觀念正確,斬瓜切菜;觀念不確,五里迷霧



3:32:07

10.4. Revisiting Iterators

10.4再訪迭代器

這樣的中文是不通的!中文語境不會對迭代器這個沒有生命的東西,甚至非人,用「訪」。訪字一定是用在人物、人文(如古跡、名勝、地域、人文地理)身上。我們會去「訪問」機器嗎?狗屁不通,莫此為甚矣。連動物,我們都不但說去「訪」,何況無生物。

再看看迭代器(iterator)

訪 ②單字想複詞 訪問、訪視、訪察、探訪、採訪……

都和「看」有關。去訪問,就是去看看。訪與看,也有著類似表哥原理

七星大法 五官會通 看看(眼官、肉體)→瞭解(心官、精神)

再對迭代器(iterator)做更進一步的瞭解

這樣的翻譯您說能不誤國這民嗎?難怪國民國文程度老是提振不起來。一日曝之,十日寒之啊!

3:41:40

程式庫還在iterator標頭檔中定義了許多種類的迭代器(iterator)(並非為容器定義的)

Insert iterators

插入迭代器 ⑧找對主詞 什麼東東插入什麼啊?

將元素插入到此迭代器關聯的容器

Stream iterators

資料流迭代器。是與輸入輸出資料流關聯的迭代器。可以巡覽資料流內各個元素

Reverse iterators

反相(反向、倒轉、倒行逆施、對反、逆反、反置)迭代器

《史記‧伍子胥列傳》:「吾日暮途遠,吾故倒行而逆施之。」

此「施」不是「布施、施予」,乃「施從良人之所之」之「施」,即「行」也。⑥相對位置有相關字義

這些迭代器運行advance的方向是反方向的。

程式庫定義的容器,除了foreward_list外都有反向迭代器

• Move iterators 移動元素用的迭代器: These special-purpose iterators move rather than copy their elements.

這些特殊用途的迭代器會移動而非拷貝它們的元素。好像迭代器會移動和拷貝元素一樣

→用這些特別的迭代器的目的是去移動容器內的元素,而非為了取得其值

⑧找對主詞 誰在move?move什麼?移動容器內的元素也。應該是說用這些迭代器去移動元素或拷貝元素,而不是迭代器本身是元素的拷貝,當然就更不是元素的移動。

就是指解參考迭代器就可以對元素做出一份拷貝(其值)

4:13:55

10.4.1. Insert Iterators

插入迭代器是一個迭代器轉接器(adaptor),它帶了一個容器的引數,回傳一個能夠插入元素到那個容器的迭代器



頁402

4:23:59

Table 10.2. Insert Iterator Operations

表10.2 :插入迭代器運算

it = t 在it目前代表的位置上插入t這個值。取決於插入迭代器的種類, 並假設c是it所繫結的容器,呼叫c.push_back(t)、c.push_ front(t)或 c.insert(t, p),其中 p 是給入 inserter 的迭代器 位置。

*it、++ it、it++ 這些運算存在,但不會對it做任何事。這每個運算子都會回傳it。

不做任何事?難道也不會解參考(dereference)嗎?



有三種插入迭代器(inserter),這三種之所以不同,在於它們會在何處(where)插入元素

back_inserter 回傳一個調用push_back()的迭代器。所以要對一個容器用到back_inserter就一定要該容器型別有一個push_back()的成員函式才行。同理,用front_inserter也是要有push_front()在其中才行。

front_inserter 回傳一個調用push_front()的迭代器

inserter 回傳一個調用insert()的迭代器

元素經由這個方式插入的都是前位插入的(即在迭代器指定的位置之前插入元素)



4:39:49

inserter creates an iterator that uses insert . This function takes a second argument, which must be an iterator into the given container. Elements are inserted ahead of the element denoted by the given iterator.

可見「inserter」是一個「函式」!

中文版漏掉此句沒翻

• inserter創建一個使用insert的迭代器。元素會被插到給定的迭代器所表示的元素

4:45:00

It is important to understand that when we call inserter(c, iter), we get an iterator that, when used successively, inserts elements ahead of the element originally denoted by iter. That is, if it is an iterator generated by inserter, then an assignment such as

* it = va1;

behaves as

it = c.insert(it, val); // it points to the newly added element

++it; // increment it so that it denotes the same element as before

4:48:52

由front_inserter回傳的迭代器表現就與inserter的很不同

5:5:00

list < int> lst = { 1, 2, 3, 4 };

list<int> lst2, lst3; // empty lists

// after copy completes, 1st2 contains 4 3 2 1

copy(lst.cbegin(), lst.cend(), front_inserter(lst2));//copy演算法,頁382

// after copy completes, 1st3 contains 1 2 3 4

copy(lst.cbegin(), lst.cend(), inserter(lst3, lst3.begin()));

pdf英文版 lst都誤成1st,數字不能在識別項開頭。

front_inserter 插入的結果是原序列的反向(reverse),而inserter、back_inserter 就是順向(不變)

頁403

練習10.26

5:9:40

三種不同的插入器是它們調用它們關聯的容器的成員函式不同,且front_inserter插入的結果與原序列順序也不同。而inserter與back_inserter則保持原序

練習10.27

unique、unique_copy汰重演算法(algorithm)

#include<vector>

#include<string>

#include <iostream>

#include <algorithm>

#include <iterator>

#include <list>

using namespace std;

int main() {

list<string>lst;

vector<string>v;// {"戒", "定", "慧", "戒"};

string w;//按住Ctrl+Alt再加滑鼠左鍵就可以多重選取或多重插入點

while (cin >> w) v.push_back(w);

//不排序就無法正確汰重

sort(v.begin(),v.end());

//以下三者均可,唯front_inserter結果元素是倒序

unique_copy(v.begin(), v.end(), front_inserter(lst));

//unique_copy(v.begin( ), v.end(), back_inserter(lst));

//unique_copy(v.begin(), v.end(), inserter(lst,lst.begin()));

for (string s : lst) cout << s << " ";

cout << endl;

}

5:43:30

練習10.28

#include<vector>

#include<string>

#include <iostream>

#include <algorithm>

#include <iterator>

#include <list>

#include <deque>

#include <forward_list>

using namespace std;

int main() {

vector<unsigned>v{ 1,2,3,4,5,6,7,8,9 };

list<unsigned>lst;

deque<unsigned>dq;

forward_list<unsigned>fw;

copy(v.cbegin(), v.cend(), inserter(lst, lst.begin()));

for (unsigned s : lst) cout << s << " ";

cout << endl; lst.clear();

copy(v.cbegin(), v.cend(), back_inserter(lst));

for (unsigned s : lst) cout << s << " ";

cout << endl; lst.clear();

copy(v.cbegin(), v.cend(), front_inserter(lst));

for (unsigned s : lst) cout << s << " ";

cout << endl;

cout << endl;

copy(v.cbegin(), v.cend(), inserter(dq, dq.begin()));

for (unsigned s :dq) cout << s << " ";

cout << endl; dq.clear();

copy(v.cbegin(), v.cend(), back_inserter(dq));

for (unsigned s : dq) cout << s << " ";

cout << endl; dq.clear();

copy(v.cbegin(), v.cend(), front_inserter(dq));

for (unsigned s : dq) cout << s << " ";

cout << endl;

cout << endl;

//copy(v.cbegin(), v.cend(), inserter(fw, fw.begin()));//forward_list也沒有insert

//copy(v.cbegin(), v.cend(), back_inserter(fw));//forward_list沒有push_back

copy(v.cbegin(), v.cend(), front_inserter(fw));

for (unsigned s : fw) cout << s << " ";

cout << endl;

}

10.4.2. iostream Iterators

6:5:52

An istream_iterator (Table 10.3 (overleaf)) reads an input stream, and an ostream_iterator (Table 10.4 (p. 405)) writes an output stream.

These iterators treat their corresponding stream as a sequence of elements of a specified type.

Using a stream iterator, we can use the

generic algorithms to read data from or write data to stream objects.

也就是說利用stream的迭代器,我們才能將通用演算法generic algorithms(泛型運算)用在stream資料流上頭

6:58:20

Operations on istream_iterators

定義istream_iterator、ostream_iterator物件時,一定要指定它所要讀取的資料型別,且該型別也必須要有對輸入、輸出運算子的定義(即具備輸入、輸出運算子的型別)

istream_iterator<T>in(is);

(is)就是把istream_iterator<T> in繫結(bind)到了is。即用is來初始化一個in。in、end都只是變數名稱,只要合語法,取什麼都行。

而若省略掉(is),就是預設初始化,此時迭代器將會是一個尾端後迭代器(off-the-end iterator):

istream_iterator<T>end;

也就是在建構一個資料流迭代器時,我們可以用所屬的資料流來初始化該迭代器,使其與該資料流相繫結,也可以省略以資料流來初始化,而取得一個尾端後的資料流迭代器



頁404

解參考(dereference)和後綴遞增 (postfix increment)運算子



What is more useful is that we can rewrite this program as

更實用的(寫法)是 ⑤添字還原 (寫法)

即更有效率的寫法是

istream_iterator<int> in_iter(cin), eof; // read int s from cin

vector<int> vec(in_iter, eof); // construct vec from an iterator range

如前所見演算法等,需要給一個迭代器範圍的,都是要巡覽該範圍中所有元素一遍的,所以vec這裡的建構器,其中運算必也類似如此

Table 10.3. istream_iterator Operations

istream_iterator<T>in(is); in reads values of type T from input stream is.

istream_iterator<T>end; Off-the-end iterator for an istream_iterator that reads values of type T.

in1 ==in2、in1 !=in2 in1 and in2 must read the same type. They are equal if they are both the end value or are bound to the same input stream.

*in Returns the value read from the stream.

in->mem Synonym for (*in).mem.

++in, in++ Reads the next value from the input stream using the >> operator for the element type. As usual, the prefix version returns a reference to the incremented iterator. The postfix version returns the old value.

(is) 這裡括號就是建構器

表 10.3 : istream一iterator 運算

istream_iterator<T> in(is); in會從資料流is讀取型別為T的值。

istream iterator<T> end; 一個讀取型別為 T 的值的 istream iterator 的off-the-end (結尾後)迭代器。

in1 == in2

in1 != in2 in1和in2必須讀取相同的型別。如果它們都是結尾值或都繫結到相同的輸入資料流,它們就相等。

*in 回傳讀自資料流的值。

in->mem (*in).mem的同義詞。

++in 、 in++ 使用該元素型別的 >> 運算子從輸入資料流讀取下個值。一如以往,前綴版本會回傳一個參考指向遞增後的迭代器。後綴版本會回傳舊的值。



Using Stream Iterators with the Algorithms

將演算法用於資料流迭代器


7:33:30

資料流迭代器與演算法一起使用

Because algorithms operate in terms of iterator operations,

因為演算法是透過迭代器運算來運作的

藉由迭代器(的名義)

accumulate and istream_iterators:

#include<iostream>

#include<iterator>

#include<numeric>

using namespace std;

int main() {

istream_iterator<int>in(cin), end;

cout<<accumulate(in, end, 0)<<endl;

}

7:53:30

頁405

istream_iterators Are Permitted to Use Lazy Evaluation

istream_iterator 可使用惰性估算(Lazy Evaluation)

也就是若是沒用上,即使定義了資料流迭代器,也不會即時去讀取資料流

The implementation is permitted to delay reading the stream until we use the iterator.

最遲最遲在我們打算解參考一個資料流迭代器前,它是一定會與資料流作繫結的

8:4:20

Operations on ostream_iterators

只要支援輸出運算子的型別都可以拿來定義輸出該型別的資料流迭代器

當我們建構一個ostream_iterator,我們可以指定第2個引數,這個引數是一個null值結尾的字元陣列(字元字串) 這個字元字串可在每個元素輸出時緊接著輸出

這個字元字串必須是C式的字元字串,即一個字串字面值(string literal),或一個null結尾的陣列指針。

That string must be a C-style character string (i.e., a string literal or a pointer to a null-terminated array).

那個字串必須是一個C-style的字元字串(即一個字串字面值,或指向一個null-terminated陣列的一個指標)。

可見C式字元字串就是一個字串字面值(string literal),或者一個以null結尾的字元陣列的指針

和istream_iterator不一樣的地方是:

We must bind an ostream_iterator to a specific stream. There is no empty or off-the-end ostream_iterator .

建構ostream_iterator的引數(繫結對象stream)不能省略!因為既然要輸出,怎麼能沒有資料(流)呢?!而輸入,本來就可以是空的,以待填入的。



6:31:22 8:12:20 下表英文版重複了(在前已有):

Table 10.4. ostream Iterator Operations

ostream_iterator<T>out(os); out writes values of type T to output stream os.

ostream_iterator<T>out(os, d); out writes values of type T followed by d to output stream os. d points to a null-terminated character array.

out=val Writes val to the ostream to which out is bound using the << operator. val must have a type that is compatible with the type that out can write.

*out, ++out,out++ These operations exist but do nothing to out. Each operator returns out.

表 10.4 : ostream_iterator 運算

ostream_iterator<T>out(os); out將型別為T的值寫到輸出資料流os。

ostream_iterator<T>out(os,d); out將後面跟著d的,型別為T的值寫到輸出資料 流os。d指向一個null-terminated字元陣列。

out = val 使用 <<運算子將val寫到out所繫結的ostream。val的型別 必須與out可以寫入的型別相容。

*out、++out、out++ 這些運算存在,但不會對out做任何事。這每個運算子都會回傳 out °

8:22:00

ostream_iterator<T> out_iter(cout, " ");

for (auto e : vec)

*out_iter++ = e; // the assignment writes this element to cout

cout << endl;

輸出完每個元素e,其後加一個" "

Each time we assign a value to out_iter , the write is committed.

所以ostream_iterator不是像istream_iterator是lazy的

It is worth noting that we can omit the dereference and the increment when we assign to out_iter .

解參考與遞增運算子在此竟能省略!

頁406

*與++不會在一個ostream_iterator上做任何事,所以省略它們對我們的程式沒有影響。



8:32:00

copy演算法的妙用:

印出容器元素

Rather than writing the loop ourselves, we can more easily print the elements in vec by calling copy:

copy(vec.begin(), vec.end(), out_iter);

cout << endl;

帶3個迭代器引數的copy在前練習10.27已做過unique_copy,第3個是目的容器的迭代器,在這裡目的容器即「ostream」因為是用cout建構出來(繫結得來)的

ostream_iterator<T> out_iter(cout, " ");

前二個引數是輸入範圍(input range),後面迭代器則指向輸出的對象(容器、序列……)

由此也可以看出為什麼ostream_iterator一定不能是空的了。若是空的、沒有繫結到任何對象(資料流),則out_iter就是無效的迭代器了(沒指向任何元素)那麼以下這式,就全毀了

copy(vec.begin(), vec.end(), out_iter);

所以,就像聖賢教育或國文教育,都是有道理的,不是列一大堆規規矩矩來綁住妳

8:42:40

Using Stream Iterators with Class Types

資料流迭代器與類別型別並用

資料流迭代器用在類別型別上

只要該型別具有輸入運算子的定義,就可以用該到來建構一個istream_iterator.

ostream_iterator也類似。

而本書中定義的「Sales_item」也同時具有輸出與輸入運算子,所以可以使用「IO iterators」來改寫頁24所載的書店程式

istream_iterator<Sales_item> item_iter(cin), eof;

ostream_iterator<Sales_item> out_iter(cout, "\n");

// store the first transaction in sum and read the next record

Sales_item sum = *item_iter++;

while (item_iter != eof)

{

// if the current transaction (which is stored in item_iter) has the same ISBN

if (item_iter->isbn() == sum.isbn())

sum += *item_iter++; // add it to sum and read the next transaction

else

{

out_iter = sum; // write the current sum

sum = *item_iter++; // read the next transaction

}

}

out_iter = sum; // remember to print the last set of records



out_iter應該也是要寫成*out_iter++才合頁406開頭所講的規矩(養成好習慣 best practices)

畢竟迭代器歸迭代器,而類別物件是類別物件,不宜相混。

留言

熱門文章