C++ STL中一些既是成员函数又是算法函数

在C++的标准模板库(STL)中,有一些函数既可以作为容器的成员函数,又有其对应的算法函数版本。这些函数在设计上非常巧妙,允许开发者在不同的场景下灵活使用它们。通常,我们将这些函数分别称为“成员函数版本”和“非成员函数版本”或“算法版本”。

这种设计不仅使得代码更具可读性和可维护性,还增强了STL的通用性和适应性。在本文中,我们将深入探讨这些函数的具体应用场景,并分析它们在C++编程中的重要性。

 常见的双重身份函数

在C++标准模板库(STL)中,有一些函数既可以作为某些容器的成员函数,又可以作为独立的算法函数,以下是几个常用的函数的详细讲解: 

1. empty()

 成员函数:几乎所有标准容器(如`vector`、`list`、`map`等)都有一个`empty()`成员函数,用于检查容器是否为空。

std::vector<int> vec;
if (vec.empty()) {
    // 容器为空
}

算法函数:C++17引入了全局算法`std::empty`,它不仅可以用于标准容器,还可以用于数组和其他支持`empty()`成员函数的自定义容器。

int arr[5];
bool is_empty = std::empty(arr); // 返回false,因为数组有元素
if (std::empty(arr)) {
    // 判断容器是否为空
}

2. size()

成员函数:大多数标准容器都有一个`size()`成员函数,用于返回容器中元素的数量。

std::vector<int> vec = {1, 2, 3};
auto size = vec.size();  // size == 3

算法函数:C++17引入了全局算法`std::size`,它可以用于标准容器、数组和其他支持`size()`成员函数的容器。

auto size = std::size(vec);  // size == 3
int arr[] = {1, 2, 3, 4};
auto size = std::size(arr); // 返回4

3.data()

成员函数:如`vector`、`string`等容器提供`data()`成员函数,用于返回指向容器内部数据的指针。

std::vector<int> vec = {1, 2, 3};
int* p = vec.data();

算法函数:C++17引入了全局算法`std::data`,可以用于标准容器、数组或其他支持`data()`成员函数的对象。

int arr[] = {1, 2, 3, 4};
int* data_ptr = std::data(arr); // 返回指向arr的指针

4. begin() 和 end()

成员函数:

  • 定义begin()end()成员函数分别返回指向容器起始和末尾的迭代器。
  • 应用容器:这些函数几乎在所有STL容器中都存在,例如std::vector, std::list, std::deque, std::set, std::map等。
std::vector<int> vec = {1, 2, 3};
auto it_begin = vec.begin();
auto it_end = vec.end();

算法函数:

  • 定义std::beginstd::end是泛型算法函数,能够返回适用于任何类型(包括C数组)的迭代器。
  • 适用场景:不仅适用于STL容器,还可以用于原生数组或其他实现了beginend的容器。
auto it_begin = std::begin(vec);
auto it_end = std::end(vec);
int arr[] = {1, 2, 3, 4};
auto it_begin = std::begin(arr); // 指向arr的第一个元素
auto it_end = std::end(arr); // 指向arr的末尾元素之后的位置

5. swap()

成员函数:大多数标准容器(如`vector`、`deque`、`map`等)都提供`swap()`成员函数,用于交换两个同类型容器的内容。

std::vector<int> vec1 = {1, 2, 3};
std::vector<int> vec2 = {4, 5, 6};
vec1.swap(vec2);  // 交换vec1和vec2的内容

算法函数:STL提供了全局算法`std::swap`,用于交换两个同类型对象的内容。这个函数不仅可以交换容器的内容,还可以交换普通变量。

std::vector<int> vec1 = {1, 2, 3};
std::vector<int> vec2 = {4, 5, 6};
std::swap(vec1, vec2);

特殊情况

std::list是一个特殊的序列容器,是一个双向链表,不支持随机访问迭代器。它为某些操作提供了专门的成员函数版本(如sort(), reverse(), remove())。这些成员函数版本针对链表结构进行了优化,通常比通用算法版本更高效。

1. reverse()

成员函数:STL中的容器类并没有直接提供reverse()作为成员函数,但std::list容器是个例外。std::list有一个reverse()成员函数,它可以直接反转链表中的元素顺序。

std::list<int> lst = {3, 1, 2};
lst.reverse();  // 现在lst为{1, 2, 3}

算法函数:STL提供了一个全局算法std::reverse,可以用于任何支持随机访问迭代器或双向迭代器的容器(如vectordeque等),来反转容器中的元素顺序。

std::vector<int> vec = {3, 1, 2};
std::reverse(vec.begin(), vec.end());  // 现在vec为{1, 2, 3}

区别:

  • 成员函数版本可能对特定容器(如list)有优化。
  • 算法版本更通用,可用于任何双向迭代器。

 2. sort()

成员函数:标准容器如vectordequelist等,并没有sort()成员函数。这意味着不能通过调用容器的成员函数来对元素进行排序。不过,需要特别注意的是,std::list容器是个例外,它提供了sort()成员函数,因为std::list是一个双向链表,无法使用随机访问迭代器。

std::list<int> lst = {3, 1, 2};
lst.sort();  // 现在lst为{1, 2, 3}

算法函数:STL提供全局算法`std::sort`,用于对支持随机访问迭代器的容器(如`vector`、`deque`等)进行排序。

std::vector<int> vec = {3, 1, 2};
std::sort(vec.begin(), vec.end());  // 现在vec为{1, 2, 3}

区别:

  • 成员函数版本针对链表结构优化,不需要随机访问迭代器。
  • 算法版本更通用,但要求随机访问迭代器。对于大多数容器(如vector)通常更高效。

3.remove()

 remove 主要作为算法函数存在,而不是普遍的容器成员函数。某些特定容器如 std::list 提供了成员函数 remove,实现实际的删除操作。

算法函数:std::remove 是一个泛型算法,它从指定范围中移除与给定值相等的所有元素,并将这些元素移动到范围的末尾。需要注意的是,它并不实际从容器中删除元素,而是将不需要的元素覆盖掉,并返回一个新的尾迭代器,指向新的“逻辑末尾”。

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

int main() {
    std::vector<int> vec = {1, 2, 3, 2, 4, 5, 2};
    
    auto new_end = std::remove(vec.begin(), vec.end(), 2);
    
    // 输出去除 2 之后的内容,但实际容器大小没有改变
    for (auto it = vec.begin(); it != new_end; ++it) {
        std::cout << *it << " ";  // 输出: 1 3 4 5
    }
    std::cout << std::endl;
    
    // 如果需要实际删除这些元素,需要调用 vec.erase(new_end, vec.end());
    vec.erase(new_end, vec.end());
    
    // 此时容器的大小变小,元素也被删除了
    for (int v : vec) {
        std::cout << v << " ";  // 输出: 1 3 4 5
    }
    std::cout << std::endl;
    
    return 0;
}

成员函数:标准容器(如 vectorlistdeque 等)并没有提供 remove 作为成员函数。相反,某些容器如 std::list 提供了一个名为 remove 的成员函数,它的作用与 std::remove 类似,但会实际删除元素。

#include <iostream>
#include <list>

int main() {
    std::list<int> lst = {1, 2, 3, 2, 4, 5, 2};
    
    lst.remove(2);  // 实际删除所有等于 2 的元素
    
    for (int v : lst) {
        std::cout << v << " ";  // 输出: 1 3 4 5
    }
    std::cout << std::endl;
    
    return 0;
}

 区别:

  • 成员函数版本直接修改容器,移除元素并调整大小。
  • 算法版本不会改变容器大小,需要额外的erase()调用。

小结

这些函数在C++ STL中的存在形式为程序员提供了更大的灵活性。根据需求,可以选择使用成员函数或全局算法函数。

  • 成员函数版本:成员函数则直接与容器的实现关联(通常限于特定的容器),适用于具体容器操作,代码简洁性要求高的情况,或者当你需要利用容器的特定功能时。
  • 算法函数版本:全局算法函数通常更通用,支持更多类型的容器或数据结构,适用于需要通用性、灵活性以及跨容器类型操作的场景。

不同容器中的成员函数和算法函数

在使用时,一般来说,如果容器有对应的成员函数,优先使用成员函数版本,因为它们通常针对特定容器进行了优化。如果需要更通用的解决方案,或者处理的不是整个容器而是一个范围,那么使用算法函数版本可能更合适。

find函数 

成员函数版本

适用容器:find 成员函数只存在于关联容器(如 std::set, std::map, std::multiset, std::multimap)中。

std::set<int> s = {1, 2, 3, 4, 5};
auto it = s.find(3);
if (it != s.end()) {
    std::cout << "Found: " << *it << std::endl;
} else {
    std::cout << "Not found" << std::endl;
}

算法函数版本

适用容器:std::find 是一个通用算法,可以用于任何支持迭代器的容器。

std::vector<int> v = {1, 2, 3, 4, 5};
auto it = std::find(v.begin(), v.end(), 3);
if (it != v.end()) {
    std::cout << "Found: " << *it << std::endl;
} else {
    std::cout << "Not found" << std::endl;
}

 区别:

  • 成员函数版本只能用于关联容器,但通常更高效(O(log n)复杂度)。
  • 算法版本可用于任何容器,但对于非关联容器是线性搜索(O(n)复杂度)。

count函数

成员函数版本

适用容器:count 成员函数只存在于关联容器中,如 std::set, std::map, std::multiset, std::multimap

std::multiset<int> ms = {1, 2, 2, 3, 3, 3};
auto count = ms.count(3);
std::cout << "Count of 3: " << count << std::endl;  // 输出: 3

算法函数版本

适用容器:std::count 是一个通用算法,可以用于任何支持迭代器的容器。

#include <iostream>
#include <vector>
#include <algorithm> // 包含 std::count

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

    // 计算 vector 中值为 2 的元素个数
    int count_of_twos = std::count(vec.begin(), vec.end(), 2);

    std::cout << "Number of 2s in the vector: " << count_of_twos << std::endl;

    return 0;
}

 比较和使用建议

  1. 效率

    • 关联容器:对于关联容器,成员函数版本的 findcount 通常比算法版本更高效,因为它们能够利用容器的内部结构(如平衡二叉树)来实现对数时间复杂度的查找。
    • 其他容器:对于如 std::vectorstd::deque 等容器,算法函数版本的 findcount 是唯一的选择,因为这些容器不提供相应的成员函数。
  2. 灵活性

    • 算法函数版本:算法函数版本更通用,可以应用于任何支持迭代器的容器或范围。它们使得代码可以在不同容器类型之间更容易复用。
    • 成员函数版本:成员函数版本与特定容器类型紧密耦合,语法上可能更简洁,但不具备算法函数的通用性。
  3. 使用建议

    • 对于关联容器(如 std::set, std::map),优先使用成员函数版本,因为它们更高效。
    • 对于非关联容器(如 std::vector, std::deque),使用算法函数版本,因为这些容器没有相应的成员函数。
    • 在编写通用代码时,使用算法函数版本可以提高代码的通用性和适应性。

总结 

在C++标准模板库(STL)中,许多函数既可以作为容器的成员函数使用,也可以通过对应的算法函数来实现。这种设计为开发者提供了极大的灵活性,使得代码不仅更具可读性和可维护性,也增强了STL的通用性和适应性。以下是对这些双重身份函数的总结:

  1. 成员函数版本与算法函数版本的灵活性:成员函数版本通常与特定容器的内部实现密切相关,因此在某些情况下(如std::listreverse()sort())能提供更高效的操作。另一方面,算法函数版本更加通用,可以应用于支持迭代器的各种容器,不受具体容器类型的限制。

  2. 常见的双重身份函数:如empty()size()data()begin()end()swap()等,这些函数既有成员函数形式,也有全局算法函数形式。成员函数通常在特定容器中更高效,而算法函数则适用于更多的场景。

  3. 特定容器的优化:对于std::list这样的容器,它们提供了一些成员函数(如sort()reverse()remove()),这些函数经过优化,能够更有效地处理链表结构。对于其他容器,则可以使用通用的算法函数版本来实现类似的功能。

  4. 关联容器的特殊情况:在关联容器(如std::setstd::map)中,成员函数版本的find()count()由于能够利用容器的内部结构,通常比算法函数版本更高效。在其他容器中(如std::vectorstd::deque),则只能使用算法函数版本来进行查找和计数。

  5. 使用建议:对于特定容器,如关联容器,优先选择成员函数版本以获得更好的性能。在编写通用代码时,使用算法函数版本可以提高代码的适应性和可重用性。在需要针对特定容器进行优化时,成员函数版本往往是更好的选择。

综上所述,了解并灵活运用这些函数的双重身份,可以帮助开发者在C++编程中编写更高效、更易维护的代码。

  • 14
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值