学懂C++(四十八):深入详解C++ STL之适配器(Adapters)

目录

适配器的概念

容器适配器

std::stack

std::queue

std::priority_queue

迭代器适配器

std::reverse_iterator

std::insert_iterator

函数适配器

std::bind

Lambda表达式

适配器的底层原理

适配器的详细使用

经典示例

使用std::stack实现DFS

使用std::priority_queue实现Dijkstra算法

总结


        C++ 标准模板库(STL)是C++中一个功能丰富且高效的库,它提供了多种数据结构和算法。在STL中,适配器(Adapters)是一类特别有用的工具,能够修改容器、迭代器和函数的行为,使其适应特定的需求。本文将深入探讨STL中的适配器,涵盖其概念、底层原理、详细使用及经典示例。

适配器的概念

适配器是一种设计模式,它使一个接口与另一个接口兼容。STL中的适配器主要分为三类:

  1. 容器适配器(Container Adapters):修改标准容器的行为。
  2. 迭代器适配器(Iterator Adapters):修改迭代器的行为。
  3. 函数适配器(Function Adapters):修改函数对象或函数指针的行为。

容器适配器

容器适配器是在已有的序列容器基础上构建的,它们提供了一个修改后的接口来满足特定的需求。STL中提供了三种容器适配器:

  1. std::stack: 后进先出(LIFO)数据结构。
  2. std::queue: 先进先出(FIFO)数据结构。
  3. std::priority_queue: 基于优先级的队列。
std::stack

std::stack基于deque(双端队列)或其他任何符合序列容器要求的容器(如vector和list)实现。它只允许在一端进行插入和删除操作。

#include <stack>
#include <iostream>

int main() {
    std::stack<int> s;
    s.push(10); 
    s.push(20); 
    s.push(30); 

    while (!s.empty()) {
        std::cout << ' ' << s.top();
        s.pop();
    }
    return 0;
}

在上述例子中,std::stack对象s使用默认的std::deque作为底层容器。通过pushpop操作进行元素的插入和删除,top操作可以访问栈顶元素。

std::queue

std::queue同样基于deque或list实现。它允许在一端插入元素(push),在另一端删除元素(pop)。

#include <queue>
#include <iostream>

int main() {
    std::queue<int> q;
    q.push(10);
    q.push(20);
    q.push(30);

    while (!q.empty()) {
        std::cout << ' ' << q.front();
        q.pop();
    }
    return 0;
}

上述代码展示了如何使用std::queuefront函数访问队列的第一个元素,而back函数访问最后一个元素。

std::priority_queue

std::priority_queue基于堆(通常是二叉堆)实现。它支持最高优先级的元素优先出队。

#include <queue>
#include <vector>
#include <iostream>

int main() {
    std::priority_queue<int> pq;
    pq.push(30);
    pq.push(10);
    pq.push(20);

    while (!pq.empty()) {
        std::cout << ' ' << pq.top();
        pq.pop();
    }
    return 0;
}

在这个例子中,std::priority_queue默认是一个最大堆(max-heap),最高优先级的元素(最大的元素)首先被访问。

迭代器适配器

迭代器适配器修改迭代器的行为,STL提供了三种常用的迭代器适配器:

  1. std::reverse_iterator:反向遍历容器。
  2. std::insert_iterator:在特定位置插入元素。
  3. std::istream_iteratorstd::ostream_iterator:用于流的输入和输出。
std::reverse_iterator

std::reverse_iterator允许反向遍历容器。

#include <vector>
#include <iostream>
#include <algorithm>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    std::reverse_iterator<std::vector<int>::iterator> rbegin(v.end());
    std::reverse_iterator<std::vector<int>::iterator> rend(v.begin());

    std::for_each(rbegin, rend, [](int x) {
        std::cout << x << ' ';
    });
    return 0;
}

在这个例子中,反向迭代器rbeginrend用于反向遍历v中的元素。

std::insert_iterator

std::insert_iterator用于在特定位置插入元素。

#include <vector>
#include <iterator>
#include <iostream>
#include <algorithm>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    std::vector<int> v2;

    std::copy(v.begin(), v.end(), std::inserter(v2, v2.begin()));

    for (int x : v2) {
        std::cout << x << ' ';
    }
    return 0;
}

在这个例子中,std::inserter适配器用于将元素插入到v2的开头。

函数适配器

函数适配器修改函数对象或指针的行为。C++11之后,标准库提供了一些新的工具,如std::bind和lambda表达式,使得以前的函数适配器(如std::bind1ststd::bind2nd)变得不那么必要。

std::bind

std::bind允许绑定函数参数,从而创建新的函数对象。

#include <functional>
#include <iostream>

void print(int x, int y) {
    std::cout << x << ' ' << y << std::endl;
}

int main() {
    auto bound_func = std::bind(print, 10, std::placeholders::_1);
    bound_func(20);  // 输出 10 20
    return 0;
}

在这个例子中,std::bind创建了一个新的函数对象bound_func,它将print函数的第一个参数固定为10,而第二个参数仍然是可变的。

Lambda表达式

Lambda表达式是创建匿名函数的一种简洁方法。

#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    std::for_each(v.begin(), v.end(), [](int x) {
        std::cout << x << ' ';
    });
    return 0;
}

在这个例子中,Lambda表达式用于在std::for_each算法中定义一个内联的匿名函数。

适配器的底层原理

适配器的底层原理通常是通过组合(composition)和代理(delegation)来实现的。适配器类内部持有一个或多个底层对象,并将操作委托给这些底层对象。例如,std::stack持有一个底层容器,并通过这个容器实现pushpop等操作。

适配器的详细使用

掌握适配器的使用需要理解其接口和底层实现,从而能够根据需求选择和定制适配器。

  1. 选择适配器: 根据需求选择合适的适配器。
  2. 理解接口: 仔细阅读适配器的文档,理解其提供的方法和行为。
  3. 定制适配器: 在必要时创建自定义适配器,满足特定需求。

经典示例

使用std::stack实现DFS

深度优先搜索(DFS)通常使用栈实现。以下是一个使用std::stack实现的DFS算法:

#include <iostream>
#include <vector>
#include <stack>

void dfs(int start, const std::vector<std::vector<int>>& graph) {
    std::vector<bool> visited(graph.size(), false);
    std::stack<int> s;
    s.push(start);

    while (!s.empty()) {
        int node = s.top();
        s.pop();

        if (!visited[node]) {
            std::cout << node << ' ';
            visited[node] = true;

            for (int neighbor : graph[node]) {
                if (!visited[neighbor]) {
                    s.push(neighbor);
                }
            }
        }
    }
}

int main() {
    std::vector<std::vector<int>> graph = {
        {1, 2},
        {0, 3, 4},
        {0, 4},
        {1},
        {1, 2}
    };

    dfs(0, graph);
    return 0;
}

在这个例子中,我们使用std::stack来管理待访问的节点,实现了深度优先搜索算法。

使用std::priority_queue实现Dijkstra算法

Dijkstra算法用于求解单源最短路径问题,通常使用优先级队列实现。以下是一个使用std::priority_queue实现的Dijkstra算法:

#include <iostream>
#include <vector>
#include <queue>

struct Edge {
    int to, weight;
};

using Graph = std::vector<std::vector<Edge>>;

std::vector<int> dijkstra(int start, const Graph& graph) {
    std::vector<int> dist(graph.size(), INT_MAX);
    dist[start] = 0;

    using pii = std::pair<int, int>; // (distance, node)
    std::priority_queue<pii, std::vector<pii>, std::greater<pii>> pq;
    pq.push({0, start});

    while (!pq.empty()) {
        int d = pq.top().first;
        int u = pq.top().second;
        pq.pop();

        if (d != dist[u]) continue;

        for (const Edge& e : graph[u]) {
            if (dist[u] + e.weight < dist[e.to]) {
                dist[e.to] = dist[u] + e.weight;
                pq.push({dist[e.to], e.to});
            }
        }
    }

    return dist;
}

int main() {
    Graph graph = {
        {{1, 2}, {2, 4}},
        {{2, 1}},
        {{3, 2}},
        {}
    };

    std::vector<int> distances = dijkstra(0, graph);

    for (int i = 0; i < distances.size(); ++i) {
        std::cout << "Distance to node " << i << " is " << distances[i] << std::endl;
    }

    return 0;
}

        在这个例子中,我们使用std::priority_queue来管理待访问的节点及其距离,实现了Dijkstra算法。

总结

        STL中的适配器为C++编程提供了极大的灵活性和便利性。理解和掌握容器适配器、迭代器适配器和函数适配器的使用,不仅能够帮助你写出更加高效、简洁的代码,还能增强你对STL底层机制的理解。这篇文章详细探讨了适配器的概念、底层原理、详细使用及经典示例,希望能够为读者更好地掌握C++ STL适配器提供帮助。

  • 8
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿享天开

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值