侯捷 C++八部曲筆記匯總 - - - 持續(xù)更新 ! ! !
一、C++ 面向對象高級開發(fā)
1、C++面向對象高級編程(上)
2、C++面向對象高級編程(下)
二、STL 標準庫和泛型編程
1、分配器、序列式容器
2、關聯(lián)式容器
3、迭代器、 算法、仿函數(shù)
4、適配器、補充
三、C++ 設計模式
四、C++ 新標準
五、C++ 內(nèi)存管理機制
六、C++ 程序的生前和死后
使用一個東西,卻不明白它的道理,不高明!—— 林語堂
階段學習
使用C++標準庫
認識C++標準庫(胸中自有丘壑?。?br> 良好使用C++標準庫
擴充C++標準庫
所謂 Generic Programming
(GP
,泛型編程),就是使用 template
(模板)為主要工具來編寫程序。
-
GP
是將datas
和methods
分開來;-
Containers
和Algorithms
可各自閉門造車﹐其間以Iterator
連通即可· -
Algorithms
通過Iterators
確定操作范圍﹐也通過Iterators
取用Container
元素。
-
-
OOP(Object-Oriented Programming)
,企圖將datas
和methods
關聯(lián)在一起。
C++標準模板庫Standard Template
最重要的六大部件(Components
):容器、算法、仿函數(shù)、迭代器、適配器、分配器
-
容器(
Containers
)是class template
-
算法(
Algorithms
)是function template
(其內(nèi)最終涉及元素本身的操作,無非就是比大小!) - 迭代器(
Iterators
)是class template
- 仿函數(shù)(
Functors
)是class template
- 適配器(
Adapters
)是class template
- 分配器(
Allocators
)是class template
關系圖:
適配器(Adapters)
可以把它理解為改造器,它要去改造一些東西;也可以理解為實現(xiàn)換膚功能。
已經(jīng)存在的東西,改接口,改函數(shù)名等。。。
實現(xiàn)適配,可以使用繼承(is a)、復合(has a) 的兩種方式實現(xiàn)。
共性:STL使用 復合(has a) 來實現(xiàn)適配!
容器適配器(Container Adapters)
例如:stack
和queue
具體定義查看:序列式容器的stack
和queue
容器
- 只使用一部分以及改接口,改函數(shù)名等。。。
- 把 復合(內(nèi)涵) 的東西換一個風貌換一種風格出來!
仿函數(shù)適配器(Functor Adapters)
bind2nd(綁定第二實參)
把東西記起來,以備后面使用!
可以看到下面的這個例子,使用算法count_if
:
- 第三個參數(shù)是一個
predicate
,也就是判斷條件,有一個仿函數(shù)對象less<int>()
,但是他被仿函數(shù)適配器bind2nd
(將less
的第二個參數(shù)綁定為40
)和not1
(取反)修飾,從而實現(xiàn)判斷條件為是否小于40。
bind2nd
調(diào)用binder2nd
:
- 圖上灰色的東西就是仿函數(shù)適配器和仿函數(shù)之間的問答!
- 這里就體現(xiàn)了仿函數(shù)為什么要繼承適合的
unary_function
或者binary_function
等類的原因!
- 這里就體現(xiàn)了仿函數(shù)為什么要繼承適合的
- 還有一個細節(jié):適配器適配之后的仿函數(shù)也能夠繼續(xù)被適配:
- 所以適配器要繼承
unary_function
或者binary_function
等類,這樣才能回答另外一個適配器的問題。 - 問 bianry_fucntion 三個參數(shù)
first_argument_type
、second_argument_type
、result_type
。 - 提問前面都要加上
typename
,是為了讓編譯通過!
- 所以適配器要繼承
- 所以,仿函數(shù)必須能夠回答適配器的問題,這個仿函數(shù)才是可適配的!
相對綁定第二實參,綁定第一實參為
bind1st
not1
對一個Predicate
取反。
-
not1
是構造一個與謂詞結果相反的一元函數(shù)對象。 -
not2
是構造一個與謂詞結果相反的二元函數(shù)對象。
一層套一層,像樂高積木一樣!
bind(新型適配器)
替換了一些過時(bind1st、bind2st)的仿函數(shù)適配器!
std::bind 可以綁定:
functions
function objects
-
member functions
,_1
(占位符號)必須是某個object
地址。 -
data members
,_1
必須是某個object
地址。
返回一個function object ret
。調(diào)用ret
相當于調(diào)用上述的1,2,3或者相當于取出4.
示例:
// bind example
#include <iostream> // std::cout
#include <functional> // std::bind
// a function: (also works with function object: std::divides<double> my_divide;)
double my_divide (double x, double y) {return x/y;}
struct MyPair {
double a,b;
double multiply() {return a*b;}
};
int main () {
// 占位符的使用方法!?。。。。。?!
using namespace std::placeholders; // adds visibility of _1, _2, _3,...
//---------------------綁定function,也就是前面的1---------------------
// binding functions:
auto fn_five = std::bind (my_divide,10,2); // returns 10/2
std::cout << fn_five() << '\n'; // 5
auto fn_half = std::bind (my_divide,_1,2); // returns x/2
std::cout << fn_half(10) << '\n'; // 5
auto fn_invert = std::bind (my_divide,_2,_1); // returns y/x
std::cout << fn_invert(10,2) << '\n'; // 0.2
auto fn_rounding = std::bind<int> (my_divide,_1,_2); // returns int(x/y)
std::cout << fn_rounding(10,3) << '\n'; // 3
MyPair ten_two {10,2};
//---------------------綁定member functions,也就是前面的3---------------------
// binding members:
//member function 其實有一個看不見的實參argument :this
auto bound_member_fn = std::bind (&MyPair::multiply,_1); // returns x.multiply()
std::cout << bound_member_fn(ten_two) << '\n'; // 20
//---------------------綁定member data,也就是前面的4---------------------
auto bound_member_data = std::bind (&MyPair::a,ten_two); // returns ten_two.a
std::cout << bound_member_data() << '\n'; // 10
//-------------------------上面的bind2nd就可以替換了-------------------------
vector<int> v {15, 37, 94, 50, 73, 58, 28, 98};
int n = count_if(v.cbegin(), v.cend(), not1(bind2nd(less<int>(), 50)))
cout << "n=" << n << endl; //5
//替換
auto fn_ = bind(less<int>(), _1, 50);
cout << count_if(v.cbegin(), v.cend(), fn_) << endl; //3
return 0;
}
迭代器適配器(Iterator Adapters)
reverse_iterator
reverse_iterator
rbegin(){//取逆向的頭,就是正向的尾巴
return reverse_iterator(end());
}
reverse_iterator
rend(){//取逆向的尾巴,就是正向的頭
return reverse_iterator(begin());
}
也有五種關聯(lián)類型:
inserter
可以不用擔心copy
到的目的容器大小不匹配的問題。copy
是寫死的,我們調(diào)用copy
,希望完成在容器指定位置插入一些值!具體的實現(xiàn):
- 把相應的容器和迭代器傳入
inserter
,對容器的迭代器中的=
運算符進行重載,就能改變copy
的行為! - 因為這個是對迭代器的
=
運算符行為進行重定義,所以是迭代器的適配器。
X適配器
X
表示未知:(容器、迭代器、函數(shù),三大類之外的!)
- 包括
ostream
、istream
迭代器適配器
ostream_iterator
-
copy
都是已經(jīng)寫好的,不能再改了! - 該適配器適配的是
basic_ostream
,也是重載了=
運算符,添加輸出操作!
istream_iterator
ostream_iterator
的兄弟,cin >> x
被替換為了 x = *iit
,適配 basic_istream
。
- 不斷
++
,就不斷讀內(nèi)容。
copy
都是已經(jīng)寫好的,不能再改了!
當創(chuàng)建iit(cin)
,已經(jīng)讀入數(shù)據(jù)了!
不斷++
,就不斷讀內(nèi)容。
補充
標準庫STL周邊還有一些東西需要知道。
Hash Function
如果有一個我們自己的類,我們要怎么給這個類設計hash
函數(shù)呢?
使用類中的成員變量的hash
函數(shù)得到hash
值,然后相加,(下面左上角)這個太naive
了,可能會產(chǎn)生很多沖突。
所以使用右邊那個!
-
args
是C++11的新特性,任意多個參數(shù)都行,n
個參數(shù)的args
作為另外一個函數(shù)的輸入的時候- 先調(diào)用①,分配一個種子
seed
,再調(diào)用②; - 在②里面拆分
args
,拆分成1
+n-1
的形式,遞歸調(diào)用自身,直到args
只剩下一個參數(shù)時,調(diào)用③; - 在②中拆分時,會不斷改變種子
seed
:基本類型的hash函數(shù)
+
0x9e3779b9+
...
(越亂越好,沒有數(shù)學可言,)。
- 先調(diào)用①,分配一個種子
也是使用想法一的思想,但是加入了更多的復雜的操作,使得得到的hash code沖突更少。
tuple
一組東西的組合,可以任意指定多少個元素,這些元素可以是任意的類型。
使用示例:
tuple<string, int, int, complex<double>> t;
sizeof(t); // 32, 為什么是32,而不是28呢?啊~侯捷也無法理解??!
tuple<int, float, string> t1(41, 6.3, "test");
cout << "t1:" << get<0>(t1) << ' ' << get<1>(t1) << ' ' << get<2>(t1) << endl;
auto t2 = make_tuple(22, 44, "test2"); // t2也是一個tuple,自動推導類型
tuple_size< tuple<int, float, string> >::value; // 3
tuple_element< tuple<int, float, string> >::type; // 取tuple里面的類型
繼承的是自己,會自動形成一個類的繼承關系,注意有一個空的 tuple
類。
type traits
trivial:瑣碎的,平凡的,平淡無奇的,無關痛癢的,無價值的,不重要的。
泛化模板類,包括五種比較重要的typedef
: 默認的回答都是重要的!
typedef _false_type has_trivial_default_constructor; //默認構造函數(shù)是不重要嗎?
typedef _false_type has_trivial_copy_constructor; //拷貝構造函數(shù)是不重要嘛?
typedef _false_type has_trivial_assignment_operator; //拷貝賦值構造函數(shù)是不重要嘛?
typedef _false_type has_trivial_destructor; //析構函數(shù)是不重要嘛?
typedef _false_type is_POD_type; //是不是舊格式(struct,只有數(shù)據(jù),沒有方法)?
比如說對于int
的type traits
,五個問題的回答都不重要。一般是算法會對traits
進行提問。實用性不高。
type traits
現(xiàn)在的 traits機 ,非常智能:
- 只要把自己寫的或者系統(tǒng)自帶的類,作為
is_()::value
就能得到問題的答案,這些問題包括下面幾種,不全:
測試:
//global function template
template <typename T>
void type_traits_output(const T& x)
{
cout << "\ntype traits for type:" << typeid(T).name() << endl;
cout << "is_void\t" << is_void<T>::value << endl;
cout << "is_integral\t" << is_integral<T>::value << endl;
cout << "is_array\t" << is_array<T>::value << endl;
cout << "is_class\t" << is_class<T>::value << endl;
cout << "is_function\t" << is_function<T>::value << endl;
cout << "is_pointer\t" << is_pointer<T>::value << endl;
cout << "is_object\t" << is_object<T>::value << endl;
...
}
類型萃取機這么強的功能,是怎么實現(xiàn)的呢?下面以is_void
為例:
- 首先去掉
const
和volatile
這兩種對得到類特征無用的修飾關鍵字,做法如下(主要是用模板技巧); - 然后將去掉
cv
(就是const
和volatile
)關鍵字之后,再傳入__is_void_helper
模板類中,讓其自己匹配是不是空類型,匹配到不同的模板類,返回不同的bool
值。
cout
是一個對象object
,不是一個類,源碼如下:
- 想要用
cout
輸出自己的類型
,就可以重載<<
運算符。
movable
movable
元素會對各種容器的速度效能產(chǎn)生影響?。?!
由vector
的增長方式,對vector
的影響很大,對其他的容器影響不是很大!
moveable
指的是 move
構造、move
賦值
-
move()
:是一種淺層拷貝,當用a
初始化b
后,a
不再需要時,最好是初始化完成后就將a
析構,使用move
最優(yōu)。 - 如果說,我們用
a
初始化了b
后,仍要對a
進行操作,用這種淺層復制的方法就不合適了。
所以C++引入了移動構造函數(shù),專門處理這種,用 a
初始化 b
后,就將 a
析構的情況。這種操作的好處是:
- 將
a
對象的內(nèi)容復制一份到b
中之后,b
直接使用a
的內(nèi)存空間,這樣就避免了新的空間的分配,大大降低了構造的成本。這就是移動構造函數(shù)設計的初衷。
move的使用場景是:原來的對象不再使用。如果再用就很危險?。?!
測試函數(shù):
移動構造函數(shù)實現(xiàn)是:調(diào)用拷貝構造函數(shù),但是會將原來的對象中的成員變量置0!這樣就不會調(diào)用原對象的析構函數(shù)了!如下圖加深的部分,而且用的是引用的引用 &&
!&&
是右值引用,右值有一個很重要的性質:只能綁定到一個將要銷毀的對象。
調(diào)用移動構造函數(shù)方法,顯示調(diào)用move:
classObj_1(std::move(classObj_2))
move焊copy:
文章來源:http://www.zghlxwxcb.cn/news/detail-426457.html
注:僅供學習參考,如有不足,歡迎指正!文章來源地址http://www.zghlxwxcb.cn/news/detail-426457.html
到了這里,關于C++、STL標準模板庫和泛型編程 ——適配器、補充(侯捷)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!