2020.09.12 Essential C++ 第三章 泛型编程风格

本文探讨了泛型编程的概念,介绍了STL(标准模板库)的两大核心组件:容器和泛型算法。深入解析了泛型算法如何通过函数模板实现类型独立,以及迭代器在操作容器中的应用。同时,文章覆盖了不同类型的容器如vector、list和deque的特点和使用场景,展示了泛型算法如find、binary_search和sort的使用,并讨论了函数对象和适配器在泛型编程中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

泛型编程风格(Generic Programming)

泛型风格与泛型算法简述

Standard Template Library(STL),即标准模板库,主要由容器(container)和用以操作容器的泛型算法(generic algorithm) 两类组件构成。
泛型算法之所以 “泛型” 是因为与其要操作的元素类型无关。其主要通过 function template(函数模板) 以实现与操作对象的类型相互独立,而要使其实现“与容器无关”的窍门,就是不要直接在容器上进行操作,而是借由一对 iterator (first and last),即迭代器,以标示迭代的元素范围

指针的算术运算

检查vector中是否有所要的值,存在则返回指向其的指针,反之返回0
处理多种类型的前提是该类型定义有equality(相等)运算符

template <typename elemType>
elemType* find(vector<elemType> &vec, elemType value) // 此处vector前不要按书加const
// 编译器提示不能将const elemType*的对象返回给elemType*,但反之可以
{
    for (int i = 0; i < vec.size();i++)
    {
        if (vec[i] == value)
            return &vec[i];
    }
    return 0;
}

接下来想让find()同时可以处理array和vector,可以通过函数重载,但本节有其他方法

几个注意事项
1.当数组被传递给函数时,传递的是数组第一个元素的地址
2.通过传入数组最后一个元素的下个地址作为“标兵”以标示结束位置,以此使得原数组array从参数列表中消失
3.既可以使用指针也可以使用subscript(下标)对特定位置的元素进行访问
4.指针算术运算中,会考虑所指类型的大小

在考虑find()对vector类的实现中,要考虑到vector可能为空,因而引入begin,end两个函数

// 若非空则取回第一个元素的地址,否则返回0
template <typename elemType>
inline elemType* begin(vector<int> &vec)
    {return vec.empty() ? 0 : &vec[0];}

Iterator(泛型指针)

实现对通用容器的iterator需要利用C++的类机制,其内部的各类运算符由iterator class内相关的inline函数提供,本节先看如何定义并使用标准容器的iterator
定义iterator应提供两份信息:迭代对象(容器)的类型,iterator所指的元素类型

vector<string> svec;
// 标准库中的iterator语法
// iter指向一个vector,后者元素类型为string
// iter一开始指向svec的第一个元素
vector<string>::iterator iter = svec.begin();

// 使用STL中的iterator的更为通用的display函数
template <typename elemType>
void display(const vector<elemType> &vec, ostream &os)
{
    vector<elemType>::const_iterator iter = vec.begin();
    vector<elemType>::const_iterator end_it = vec.end();

    for (; iter != end_it;++iter)
        os << *iter << ' ';
    os << endl;
}

重新实现的find(),同时支持一对指针或一对iterator

template <typename IteratorType, typename elemType>
IteratorType find(IteratorType first, IteratorType last,
                  const elemType &value)
{
    for (; first != last;++first)
        if (value == *first)
            return first;
}

其实现基于底部元素所属类型的equality(相等)运算符。若未提供此运算符,则传入一个函数指针或运用function object

使用顺序性容器

顺序性容器用来维护一组排列有序、类型相同的元素,典型的顺序性容器有vector,list
vector随机访问(random access) 效率高,但插入、删除效率较低,list则恰好相反
第三种容器是deque(双端队列),类似于vector都以连续内存储存元素,但其对最前端、最后端的插入与删除效率更高

容器的具体使用

首先要包含相关的头文件

#include <vector>
#include <list>
#include <deque>

定义顺序性容器对象有以下五种方式

// 1.产生空的容器
list<string> slist;
vector<string> ivec;

// 2.产生特定大小的容器,每个元素以其类型的默认值作为初值(注:c++内置算术类型默认值为0)
list<int> ilist(1024);
vector<string> svec(32);

// 3.特定大小的容器,为每个元素指定相同的初值
vector<int> ivec(10, -1);
list<string> slist(16, "unassigned");

// 4.通过一对iterator产生容器,这对iterator用来标示一整组作为初值的元素的范围
int ia[8] =
    {1, 1, 2, 3, 5, 8, 13, 21};
vector<int> fib(ia, ia + 8);

// 5.根据某个容器产生新容器,复制原容器内的元素,作为新容器的初值
list<string> slist; // 当前为空,假设后续填充了该list
list<string> slist2(slist); // 进行复制操作
特殊的操作函数

push_back()末端插入 pop_back()末端删除

list和deque特有:push_front() pop_front()
注:两个pop函数不会返回被删除的元素值,故读前端使用front(),读末端使用back()

通用的insert及erase函数

insert()为通用函数,具有四种变形(在position前插入value,在position前插入类型默认值,在position前插入count个value,在position前插入iterator first~last(左闭右开)的各个元素)
erase()同上,具有两种变形(删一个,删一个范围)

使用泛型算法

使用泛型算法前,先要包含对应的algorithm头文件

#include <algorithm>

e.g:用vector储存的递增数列,构造is_elem()检索是否有elem
可采用的四种泛型搜索算法

  1. find()
  2. binary_search()
  3. count() 返回数值相符的元素个数
  4. search() 比对某个容器内是否存在某个子序列,存在则返回iterator指向子序列起始处,不存在则返回iterator指向容器末尾

设计泛型算法

泛型算法:与操作的元素类型无关,与操作的容器也无关

vector<int> less_than_10(const vector<int> &vec)
{
    vector<int> nvec;
    for (int ix = 0; ix < vec.size();++ix)
        if (vec[ix]<10)
            nvec.push_back(vec[ix]);
    return nvec;
}

bool less_than(int v1, int v2)
    {return v1<v2 ? true : false;}

bool greater_than(int v1, int v2)
    {return v1>v2 ? true : false; }


// 自定义了比较函数,通过第三个参数pred以函数指针的形式,并要求pred只能返回bool
vector<int> filter(const vector<int> &vec,
                   int filter_value,
                   bool (*pred)(int, int))
{
    vector<int> nvec;

    for (int ix = 0; ix < vec.size();++ix)
    {
        // 调用pred所指函数
        // 比较vec[ix]与filter_value
        if (pred(vec[ix], filter_value))
            nvec.push_back(vec[ix]);
    }
    return nvec;
}

从寻找vector中小于10的元素一步步扩展其弹性,最后使用函数指针来自定义其判断中的比较操作

接下来继续扩展弹性,通过find_if()替换循环,并用函数指针传入符合条件

Function Object

其本质是某种class的实例对象,由于对function call做了重载操作,其可以当做一般函数使用
引出其主要是为了代替函数指针方式,并提升效率
使用前要包含相关头文件

#include <functional>

e.g

// 使泛型算法sort()以升序排列
sort(vec.begin(), vec.end(), greater<int>());
// binary_search一行运行
binary_search(vec.begin(), vec.end(), elem, greater<int>());
Function Object Adapter

Function Object Adapter对Function Object进行操作
目的:通过adapter将二元运算转换为一元运算符(通过绑定参数的方式)
bind1st:指定值绑定到第一参数
bind2st:指定值绑定到第二参数

vector<int> filter(const vector<int> &vec,
                   int val, less<int> &lt)
{
    vector<int> nvec;
    vector<int>::const_iterator iter = vec.begin();

    // bind2nd(less<int>, val);
    // 会把val绑定与less<int>的第二个参数上
    // 则,less<int>将每个元素拿来与val比较

    while ((iter = 
            find_if(iter, vec.end(),
                    bind2nd(lt, val)))!=vec.end())
    {

        nvec.push_back(*iter);
        iter++;
    }
}

进行进一步的泛型化, 消除vector元素类型、vector容器类型的依赖关系,通过function template的方式

// 对函数中不同类型分别声明其名称以区分
template <typename InputIterator, typename OutputIterator,
            typename Elemtype, typename Comp>
OutputIterator filter(InputIterator first, OutputIterator last,
                        OutputIterator at, const Elemtype &val, Comp pred)
{
    while ((first = 
                find_if(first, last,
                    bind2nd(pred, val)))!=last)
    {
        cout << "found value: " << *first << endl;

        *at++ = *first++;
    }
    return at;
} 
另一种adapter

为所谓negator,对function object真伪取反,可实现通过function object中的less寻找最大值

使用Map

map定义为一对数值,key通常为字符串作为索引,另一数值为value
e.g 真正的字典

#include <map>
#include <string>
map<string, int> words;
// 输入方式
words["vermeer"] = 1;
words[tword] // 当tword不存在时,会被放于map内,并获得默认值0

// for循环打印整个map
map<string, int>::iterator it = words.begin();
for (; it != map.end();++it)
    cout << "key: " << it->first
         << "valu: " << it->second << endl;
// map对象first member对应于key,second对应于value,iterator通过箭头运算符访问

查询map中是否存在某个key的三种方法
1.将key作为索引直接访问,其缺点是key不在map中时,其会被自动加入并设置为默认值
2.map的find()函数(非泛型算法find())

words.find("vermeer")

在其中则返回iterator,指向key/value形成的一个pair。反之返回其end()
3.map的count()函数
其返回某特定项在map内的个数,map中最多只有一个

使用Iterator Inserter

目的:灵活处理目的端容器的大小问题,使其能在需要时进行扩展

insertion adapter

首先要包含对应头文件

#include <iterator>

其可以避免使用容器的assignment(赋值)运算符
1.back_inserter()
以容器的push_back()取代赋值运算,参数为容器本身

vector<int> result_vec;
    unique_copy(ivec.begin(), ivec.end(), back_inserter(result_vec));

2.inserter()
以容器的insert()函数取代assignment运算符,其两个参数分别为容器、指向插入操作起点的iterator

vector<string> svec_res;
    unique_copy(sevc.begin(), sevc.end(), inserter(svec_res, svec_res.end()));

3.front_inserter()以容器的push_front()函数取代assignment,只适用于list和deque

list<int> ilist_clone;
    copy(ilist.begin(), ilist.end(), front_inserter(ilist_clone));

使用iostream Iterator

通过iostream Iterator从标准输入读取,或想标准输出中写入

标准库中有供输入及输出使用的iosream iterator类,分别为istream_iterator和ostream_iterator,分别支持单一元素的读取和写入
使用前需包含iterator头文件

#include <iterator>

从输入读取:
需要一对iterator:first和last

// 绑定至标准输入,作为first
istream_iterator<string> is(cin);
// 不为其指定istream对象,即代表EOF(end of file)
istream_iterator<string> eof;
// 从标准输入写到vector中,并考虑到vector空间问题,使用back_inserter
copy(is, eof, back_inserter(text));

输出:
需要一个ostream_iterator标示字符串元素的输出位置

ostream_iterator<string> os(cout, " ");

第二个参数既可以为C-style字符串,也可以是字符串常量,表示各个元素被输出时的分隔符,默认情况下无分隔

copy(text.begin(), text.end(), os);

非标准输入输出: 将istream_iterator绑定至ifstream object,ostream_iterator绑定至ofstream object

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值