您的位置:首页 > 编程语言 > C语言/C++

重拾C++ 泛型算法

2016-08-28 10:52 190 查看
vector有一些操纵大小的操作,capacity()打印现在的大小,reserve(n)保持储存n个元素,shrink_to_fit()将capacity恢复到现在

容器的大小。reserve并不会减少空间或代替resize的能力。看来这种相当的简单。

跳过下面有关string的部分。

容器适配器,是基于一些顺序容器构造的数据结构,这些数据结构要根据基本的底层容器进行初始化。(利用已初始化的命名对象进行初始化)

但对适配器本身进行改变后,并不能对相应的底层容器进行相应的访问操作,特别地,底层容器的size等并没有改变。

这仅仅是用来初始化,(而且不能对有元素的底层容器进行初始化)而非实际存储。

栈stack提供4种操作,push pop emplace top(对应于类vector的 front end)

queue仅仅是具备先进先出的特性,priority queue具备按优先级进行排序的特点。(进出特性明确)

queue可以用vector作为底层容器实现的原因可能在于erase方法。关于优先队列适配器的部分且不述,编译器并未支持。

下面展开有关泛型算法的讨论。

泛型算法的设定是对迭代器范围内的元素进行处理。

find:查找指定元素,并返回指定元素的迭代器,如没有,返回第二个迭代器(“尾后”迭代器)。

    由于其严谨的泛型形式导致其不能在string中对子string进行查找。

将find函数应用于指针范围也是可以的,一般指针由begin end 函数(不需要加载库)得到。

泛型算法一般和序有关的操作应当依赖于容器类中的"==",但相应的操作可以通过指定谓词来进行(不需要重载操作符)。

其进行的操作可以视为迭代进行,并不改变容器大小,这可以从algorithm remove中看到。

想利用remove删除元素,可以利用remove返回移动后原迭代范围的尾后迭代器,结合erase范围删除得解。

numeric accumudate算法将范围元素进行“累加”,并将第三个参数作为初值传入。(这里存在允许类型转换的假定)

algorithm equal由于相同容器实例化类型封装了"==",equal主要用来在不同容器间进行比较,接受一个迭代范围及另一个范围

的起始迭代器。对于封装比较operator的说明,以自定义的重载运算为准。

algorithm fill向一个容器范围写入给定的元素(第三个参数)

    fill_n接受三个参数,写入的起始迭代器,写入元素的数量,写入的值。(可以将某容器元素都设定为某值)

容器库泛型算法,并不能对容器大小进行改变,与之相对的,iterator库中提供了可以插入容器元素的迭代器,

但在容器操作上并没有什么实质上的不同。仅仅是提供了不同的接口。

以iterator::back_inserter为例,利用其对某一容器进行初始化,返回一个可以执行尾部插入的迭代器,但其内部仍然调用

push_back方法。(只有能执行相关操作的容器才能这样做)

如果是单纯地使用iterator的上述相应所谓迭代器适配器,其结果与基本操作无异,但是将其作为泛型算法迭代器范围的参数,

能起到加成的作用,比如将上述back_inserter应用于fill_n 即可实现不已迭代为形式的扩张插入。

标准库begin end函数被定义在头文件iterator中,被用来返回迭代器。

对拷贝算法,其常常与back_inserter相配合使用。这里对back_inserter的看法是相对模糊的,但能够理解。

对于容器中元素进行改变的算法,一般有两个版本,一种是改变原容器,另一种是得到一个拷贝(原容器不变)

以replace为例,replace接受4个参数,前两个是迭代器范围,后两个是将某值替换为其它值。这是一个该变原容器的算法,

不改变的版本为replace_copy,其接受第三个参数(共5个参数)为将改变后的版本插入的位置。

对容器元素的重排(元素重载序运算)删除多调用标准库algorithm::sort 及 unique,后者返回第一个不满足唯一性的迭代器,

之后使用erase即可。sort具有可选的第三个参数作为排序的谓词(bool类型函数指定序)。

在自定义谓词进行排序时,涉及一个问题,即是新序对旧序的完全替换还是,在旧序上发展而来,

在这里,有stable_sort方法实现在首先满足新序的情况下,利用旧序对新序下相等的元素进一步排(即序的优先级问题)

标准库algorithm::find_if 对一个迭代器范围寻找第一个符合第三个参数(谓词)的迭代器。(否则返回尾后)

lambda表达式,的注意事项:lambda表达式必须包含捕获参数列表(可以是空列表),其它部分在理论上是可以省略的。

        当lambda函数体仅由一个return语句组成时。(此时与python中的lambda表达式是等价的),省略的返回值

        类型可以由编译器推断获得。(可省略)

        但当lambda函数体中除了return还有其它语句时,如果省略返回值类型,会将返回值类型置为void,应当保持返回

        值类型的写作。(虽然实现由编译器实现可能导致对的情况)

       

        auto f = [capture list](para list)-> return_type{func body;}; (注意  ;  的安排)

       

由于算法所指定接收的谓词一般仅对容器中的元素提供接口,当想要动态地将与处理结果有关的参数传入谓词时问题有一些麻烦,

这在脚本语言中是容易的(在python中,其转化为对函数提供默认参数或动态地指定某参数值的问题,利用functools.partial

可以很好地解决),但在C++中就需要利用闭包的概念。

编程中闭包的一大特性是可以将闭包置于另一个环境(函数环境)中,并使用该环境中的变量(如python函数嵌套定义),

实际在数学中就相当于对一个空间指定子空间,子空间具有空间的全部性质,可见python所定义的函数空间“太好了”。

这与数学中的闭包特性是基本吻合的。对于距离空间的一个子集,可以得到这个子集的闭包,在闭包中进行Cauchy收敛,

所收敛的点都在此闭包中,闭包是自治的。由于要求闭包的这种自治性,对于大多数支持变量即是对象的脚本语言,闭包会有较好的支持。

lambda表达式:

基本具备形式 [capture list](parameter list) -> return type{function body}

一般返回参数形式可以由function body推得,故最简形式为:
[](parameter list){function body}

注意:这里有时捕获列表没有捕获变量,但也需要给出空列表。

声明lambda表达式这一可调用对象,一般储存此部分时要声明相应的类型,

最为简单的方法是将其赋给auto进行自动类型推断,

显式声明也是可以的,采用functional库中的function模板对返回值及参数

组成的元素进行实例化即可。

如:
function<int (const int &)> b = [](const int&i){return i;};

就是保存可调用对象的一种方法。

对lambda表达式中捕获参数列表中的参数一般可以有两种操作,即改变与不改变

两种。进行改变操作时,应将其声明为引用。否则对非引用形式的捕获参数变量

在lambda函数体中进行改变会被视为非法的。(直接编译出错,提示read only 

valuable)

lambda表达式实现了闭包的概念,一般地如上初始化一个lambda表达式是采用

只闭不包的形式,即限定表达式的变量环境。

但也可以在捕获列表[]中使用= 或 & 将所有外包的环境变量包含在内部体中。

对于上面的值捕获及引用捕获方式,也可以采用混合的方法,即在捕获列表中

既声明全体的值捕获,又声明个体的引用捕获。

由于C++的编译特性,导致在lambda表达式捕获列表声明时,要求捕获的表达式在之前

声明时必须是已定义的,并且在声明后对此变量进行改变不会改变在编译期已经

决定的捕获变量值,这与python等动态类型语言是不同的。

当然这是值拷贝的情况:

Ex:
        size_t v1 = 42;
        auto f = [v1]()mutable{return ++v1;};
        v1 = 0;
        auto i = f();
        cout << i <<endl;


也有对之后进行改变的值有影响的方法,即引用:

Ex:
        size_t v1 = 42;
        auto f2 = [&v1] {return ++v1;};
        v1 = 0;
        auto j = f2();
        cout << j <<
be38
; endl;


一般指定的lambda表达式可以自动推断出返回类型,但这只限定于有一个return语句的

情况,对于多个return语句(尽管返回的类型是相同的),需要显式指定返回类型。

lambda表达式也可以有模板版本:

Ex:
template<typename T>
void Print(const T &container)
{
        for_each(container.begin(), container.end(), [](typename T::value_type t){cout << t << " ";});
        cout << endl;
}


一些如插入迭代器之类的back_inserter(back_insert_iterator) inserter等显式声明其类型是比较麻烦的,

可用auto声明类型。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息