侯捷C++ (二--STL标准库)2

2023-12-14 14:13:22

适配器 adapter

也可以叫做改造器,改造已经存在的东西

有:仿函数适配器、迭代器适配器、容器适配器
实现适配,可以使用继承、复合的两种方式实现。
共性:STL使用复合来实现适配

容器适配器

包括stack、queue,内含一个容器,这样也算一种改造

仿函数适配器


bind2nd
可以看到下面的这个例子,使用算法count_if,其中第三个参数是一个predicate,也就是判断雕件,有一个仿函数对象less<int>(),但是他被仿函数适配器bind2nd(将less的第二个参数帮定位40)和not1(取反)修饰,从而实现判断条件为是否小于40。
?

bind2nd调用binder2nd。
图上灰色的东西就是仿函数适配器和仿函数之间的问答!这里就体现了仿函数为什么要继承适合的unary_function或者binary_function等类的原因!
还有一个细节:适配器适配之后的仿函数也能够继续被适配,所以适配器要继承unary_function或者binary_function等类,这样才能回答另外一个适配器的问题。
所以,仿函数必须能够回答适配器的问题,这个仿函数才是可适配的!
?

bind

可以绑定:

functions
function objects
member functions, _1必须是某个object的地址
data members, _1必须是某个object的地址
返回一个function object ret。调用ret相当于调用上述的1,2,3或者相当于取出4.

double my_divide(double x, double y){
	return x / y;
}

---------------------绑定function,也就是前面的1---------------------
// 占位符的使用方法!!!!!!!!
using namespace std::placeholder;

auto fn_five = bind(my_divide, 10, 2);
cout << fn_five() << endl;// 输出5

auto fn_five = bind(my_divide, _1, 2);
cout << fn_five(10) << endl;// 输出5

auto fn_five = bind(my_divide, _2, _1);
cout << fn_five(10, 2) << endl;// 输出0.2

auto fn_five = bind<int>(my_divide, _2, _1);
cout << fn_five(10, 2) << endl;// 输出0,因为指定了返回类型

---------------------绑定member functions,也就是前面的3---------------------
struct MyPair{
	double a,b;
	double multiply(){ return a * b; }
}
Mypair ten_two { 10, 2 };

//member function其实有一个this指针
auto bound_menfn = bind(&MyPair::multiply, _1);
cout << bound_menfn(ten_two) << endl;// 输出20


---------------------绑定member data,也就是前面的4---------------------
auto bound_mendata = bind(&MyPair::a, ten_two);
cout << bound_mendata() << endl;// 输出10

auto bound_mendata = bind(&MyPair::b, _1);
cout << bound_mendata(ten_two) << endl;// 输出2

迭代器适配器

reverse_iterator
reverse_iterator
rbegin(){
	return reverse_iterator(end());
}
	
reverse_iterator
rend(){
	return reverse_iterator(begin());
}

inserter


可以不用担心copy到的目的容器大小不匹配的问题。
我们调用copy,希望完成在容器指定位置插入一些值,但是需要改变已经写好的copy的功能。

可以通过运算符重载来实现。把相应的容器和迭代器传入 inserter?

因为这个是对迭代器的=运算符行为进行重定义,所以是迭代器的适配器。
?

X适配器: ostream_iterator? istream_iterator

ostream_iterator

适配的是basic_ostream,也是重载=运算符,添加输出操作。

istream_iterator

cin>>x被替换为了x=*iit,适配basic_istream

STL 周围的东西

hash function

hash code希望越乱越好

如果有一个自己的类,我们要怎么给这个类设计hash函数呢?
第一种想法,使用类中的成员变量的hash函数得到hash值,然后相加,这个太naive了,可能会产生很多冲突。
所以用下面的实现方法:

tuple

用例:

tuple<string, int, int, complex<double>> t;
sizeof(t); // 32, 为什么是32,而不是28呢?啊~侯捷也不理解啊!

tuple<int, float, string> t1(41, 6.3, "test");
get<0>(t1); // 41
get<1>(t1); // 6.3
get<2>(t1); // test

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里面的类型

继承自己,这个自己是由一部分的自己组成的 (看图片右上角)

type traits


泛化模板类,包括五种比较重要的typedef
默认构造函数重要吗?
拷贝构造函数重要嘛?
拷贝赋值构造函数重要嘛?
析构函数重要嘛?
是不是旧格式(struct,只有数据,没有方法)?
默认的回答都是重要的!

比如说对于int的ttype traits,五个问题的回答都不重要。一般是算法会对traits进行提问。
实用性不高。

现在的traits机,非常智能,只要把自己写的或者系统自带的类,作为is_()::value就能得到问题的答案,这些问题包括下面几种,不全:

这么强的功能,是怎么实现的呢?下面以is_void为例:
首先去掉const和volatile这两种对得到类特征无用的修饰关键字,做法如下(主要是用模板技巧);
然后将去掉cv关键字之后,传入__is_void_helper模板类中,让其自己匹配是不是空类型,匹配到不同的模板类,返回不同的bool值。

cout

是一个对象,不是一个类,源码如下:

想要用cout输出自己的类型,就可以重载<<运算符。

moveable元素对各种容器的速度效能影响


moveable指的是move构造、move赋值
move():是一种浅层拷贝,当用a初始化b后,a不再需要时,最好是初始化完成后就将a析构,使用move最优。
如果说,我们用a初始化了b后,仍要对a进行操作,用这种浅层复制的方法就不合适了。所以C++引入了移动构造函数,专门处理这种,用a初始化b后,就将a析构的情况。这种操作的好处是:将a对象的内容复制一份到b中之后,b直接使用a的内存空间,这样就避免了新的空间的分配,大大降低了构造的成本。这就是移动构造函数设计的初衷。
移动构造函数实现是:调用拷贝构造函数,但是会将原来的对象中的成员变量置0!这样就不会调用原对象的析构函数了!如下图加深的部分,而且用的是引用的引用&&!&&是右值引用,右值有一个很重要的性质:只能绑定到一个将要销毁的对象

move的使用场景是:原来的对象不再使用。
调用移动构造函数方法,显示调用move:classObj_1(std::move(classObj_2))

OOP和GP(泛型编程)的区别


OOP是把数据和方法放在一个类里面
GP是把数据和方法分开,这么做有什么好处呢?容器和算法可以闭门造车,通过迭代器产生关联;

指定比较方法时,什么时候用仿函数,什么时候用函数呢?
使用容器指定比较方法时用仿函数,使用算法指定比较方法时使用函数。

所有算法,在操作容器的元素的时候,无非就是比较大小。
?

参考书籍:《STL源码剖析》

参考文章:侯捷C++八部曲笔记(二、STL标准库和泛型编程)_侯捷stl-CSDN博客

文章来源:https://blog.csdn.net/zaizai1007/article/details/134909766
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。