详解C++中的全局算法

全局算法

在C++中,全局算法通常指的是不依赖于特定数据结构或对象,而是可以在各种数据集合上使用的通用 算法。这些算法通常定义在标准模板库(STL)中,因此可以在整个程序中重复使用,适用于多种数据 类型。STL中的算法可以作用于数组、向量、列表、集合、映射等容器。使用这些算法时,通常需要包 含头文件 <algorithm>。

(1)遍历算法

std::for_each

std::for_each 算法用于对容器中的每个元素执行指定的函数或操作。它接受一个迭代器范围(开始和 结束迭代器)和一个函数对象(可以是函数指针、lambda表达式或函数对象类实例)。

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

using namespace std;
void Double(int &n)
{
    n *= 2;
}
int main()
{
    std::vector<int> v{ 1, 2, 3, 4, 5 };
    std::for_each(v.begin(), v.end(), Double);//函数指针
    std::for_each(v.begin(), v.end(), [](int n) { std::cout << n << ' ';});//lambda表达式
    std::cout << std::endl;
    return 0;
}

std::copy

std::copy 算法用于将一个容器中的元素复制到另一个容器中。它同样接受一个迭代器范围和一个输出 迭代器,用于指定复制的目标位置

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

int main()
{
    std::vector<int> source{ 1, 2, 3, 4, 5 };
    std::vector<int> destination;
    destination.resize(source.size());
    std::copy(source.begin(), source.end(), destination.begin());

    for (int n : destination)
    {
        std::cout << n << ' ';
    }
    std::cout << std::endl;
return 0;
}

std::transform

std::transform 算法用于对容器中的每个元素应用一个函数或操作,并将结果存储在另一个容器中。 它类似于 std::for_each ,但还允许你指定一个输出迭代器来存储转换后的结果。

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

int Double(int n)
{
    return n * 2;
}
    
int main() {

std::vector<int> v{ 1, 2, 3, 4, 5 };
std::vector<int> result;
result.resize(v.size());
std::transform(v.begin(), v.end(), result.begin(), [](int n) { return n * 2;});

    for (int n : result) {
        std::cout << n << ' ';
    }

std::cout << std::endl;
std::transform(result.begin(), result.end(), result.begin(), Double);

    for (int n : result) {
        std::cout << n << ' ';
    }
std::cout << std::endl;

return 0;
}

std::replace 和 std::replace_if

std::replace 算法用于将容器中所有等于给定值的元素替换为另一个值。而 std::replace_if 则根据 指定的条件来替换元素。

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

int main()
{
    std::vector<int> v{ 1, 2, 3, 2, 5 };
    std::replace(v.begin(), v.end(), 2, 10); // 将所有2替换为10
    
    for (int n : v)
    {
        std::cout << n << ' ';
    }

    std::cout << std::endl;
    std::replace_if(v.begin(), v.end(), [](int n) { return n % 2 == 0; }, 20);
    // 将所有偶数替换为20

    for (int n : v)
    {
        std::cout << n << ' ';
    }
    std::cout << std::endl;
return 0;
}

(2)查找算法

查找算法是在整个数据集中搜索特定元素或满足特定条件的元素的算法。C++标准库提供了一些用于查 找的算法,这些算法可以在各种容器(如向量、列表、集合等)中使用。

std::find

std::find 算法用于在给定范围内查找指定元素。它接受两个迭代器(表示范围的开始和结束)和一个 要查找的值作为参数,并返回一个指向找到的元素的迭代器,如果未找到则返回结束迭代器(end())。

#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
    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;
    }
return 0;
}

std::find_if

std::find_if 算法用于在给定范围内查找满足特定条件的第一个元素。它接受两个迭代器和一个谓词 (通常是一个函数对象、函数指针或lambda表达式)作为参数。

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

bool isEven(int n)
{
    return n % 2 == 0;
}

int main()
{
    std::vector<int> v{1, 2, 3, 4, 5};
    auto it = std::find_if(v.begin(), v.end(), isEven);
    if (it != v.end())
    {
        std::cout << "Found even: " << *it << std::endl;
    }
    else
    {
        std::cout << "No even numbers found" << std::endl;
    }
return 0;
}

std::find_first_of

std::find_first_of 算法用于在第一个范围内查找与第二个范围中任何元素匹配的第一个元素。

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

int main()
{
    std::vector<int> v1{ 1, 2, 3, 4, 5 };
    std::vector<int> v2{ 4, 5, 6 };
    auto it = std::find_first_of(v1.begin(), v1.end(), v2.begin(), v2.end());
    if (it != v1.end())
    {
        std::cout << "Found match: " << *it << std::endl;
    }
    else
    {
        std::cout << "No match found" << std::endl;
    }
    return 0;
}

std::search

std::search 算法用于在给定范围内查找与另一个范围完全匹配的子序列。

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

int main()
{
    std::vector<int> v1{ 1, 2, 3, 4, 5, 3, 4 };
    std::vector<int> pattern{ 3, 4 };
    auto it = std::search(v1.begin(), v1.end(), pattern.begin(), pattern.end());
    if (it != v1.end())
    {
        std::cout << "Pattern found at position: " << (it - v1.begin()) <<std::endl;
    }
    else
    {
        std::cout << "Pattern not found" << std::endl;
    }
return 0;
}

(3)排序算法

std::sort

std::sort 是最常用的排序函数之一,它对一个给定范围内的元素进行排序。这个函数使用快速排序、 归并排序或堆排序等算法,具体实现取决于标准库的实现和编译器的优化。

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

int main()
{
    std::vector<int> vec = { 4, 2, 5, 1, 3 };
    std::sort(vec.begin(), vec.end());
    // vec 现在为 {1, 2, 3, 4, 5}
    for(auto n : vec)
    {
        std::cout << n << " ";
    }
    std::cout << std::endl;
return 0;
}

(4)划分算法

std::partition

std::partition 是 C++ 标准库中的一个算法,它用于对容器(或序列)中的元素进行分区。该算法重 新排列元素,使得所有满足某个谓词的元素都出现在不满足该谓词的元素之前。分区操作并不保证每个 分区内部元素的相对顺序。

请注意,尽管所有偶数都出现在奇数之前,但偶数和奇数各自内部的顺序可能并不保持原样。这是 因为 std::partition 不保证稳定分区。

#include <vector>
#include <algorithm>
#include <iostream>
bool isEven(int n)
{
    return n % 2 == 0;
}
int main()
{
    std::vector<int> numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    // 使用 std::partition 将偶数放在前面,奇数放在后面
    std::partition(numbers.begin(), numbers.end(), isEven);
    // 输出分区后的元素
    for (int num : numbers)
    {
        std::cout << num << ' ';
    }
    std::cout << std::endl;
    return 0;
}

(5)迭代器算法

std::distance(InputIterator first, InputIterator last)

std::distance 是 C++ 标准库中的一个函数,定义在<iterator> 头文件中。它用于计算两个迭代器 之间的距离,即它们之间元素的数量。这个函数特别有用,因为它可以适用于各种类型的迭代器。

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

    // 获取指向第一个元素的迭代器
    std::vector<int>::iterator first = vec.begin();

    // 获取指向第三个元素的迭代器
    std::vector<int>::iterator third = first + 2;

    // 计算两个迭代器之间的距离
    std::size_t distance = std::distance(first, third);
    std::cout << "Distance between first and third element is: " << distance <<std::endl;
    return 0;
}

std::advance

std::advance 是 C++ 标准库中的一个函数,它定义在<iterator> 头文件中。 std::advance 用于 将迭代器前进(或后退)指定的距离。这个函数对于需要改变迭代器位置的情况非常有用,尤其是当迭 代器不支持直接的算术运算时(如 std::list 的迭代器)。

template<class InputIterator, class Distance>
void advance(InputIterator& it, Distance n);

//InputIterator 是迭代器的类型。
///Distance 是迭代器应该前进的距离,通常是一个整数。
//it 是目标迭代器,其位置将被改变。
//一般来说n给正数,对于双向迭代器可以给负数,表示后退

在这个例子中, std::advance 将迭代器 it 前进了3个位置,所以 *it 现在指向向量 vec 中 的第4个元素(值为4)。

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

    // 获取指向第一个元素的迭代器
    std::vector<int>::iterator it = vec.begin();

    // 将迭代器前进3个位置
    std::advance(it, 3);

    // 此时it指向第4个元素
    std::cout << "The value at the advanced iterator is: " << *it << std::endl;
    return 0;
}

std::next

std::next 是 C++ 标准库中的一个函数,它定义在 <iterator>头文件中。 std::next 用于返回指 向给定迭代器之后某个位置的迭代器。这个函数在需要获取迭代器之后某个元素的位置时非常有用。

格式:

template<class InputIt>
InputIt next(InputIt it, typename iterator_traits<InputIt>::difference_type n =1);
//InputIt 是输入迭代器的类型。
//it 是当前的迭代器。
//n 是你想要前进的步数,默认值为1。

std::next 会返回一个新的迭代器,它指向 it 之后 n 个位置。如果 n 为正数,则返回迭代器指向 it 之后的位置;如果 n 为负数,则返回迭代器指向 it 之前的位置(前提是迭代器支持后退)。

与 std::advance 不同, std::next 不会修改传入的迭代器 it ,而是返回一个新的迭代器。这意味 着你可以保留原始迭代器的位置,同时获取指向新位置的迭代器。

在这个例子中, std::next 返回了一个新的迭代器 next_it ,它指向 vec 中的第四个元素(值 为4)。同时,原始的迭代器 it 仍然指向第一个元素。

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

    // 获取指向第一个元素的迭代器
    std::vector<int>::iterator it = vec.begin();

    // 获取指向第二个元素的迭代器
    std::vector<int>::iterator next_it = std::next(it, 3);

    // 输出第四个元素的值
    std::cout << "The value at the iterator is: " << *next_it << std::endl;

    // 输出原始迭代器的值,它仍然指向第一个元素
    std::cout << "The value at the original iterator is: " << *it << std::endl;
    return 0;
}

(6)内存管理

<memory>是 C++ 标准库中的一个头文件,它提供了与内存管理相关的各种算法。这些算法和容器适配 器有助于动态管理内存,包括动态分配和释放内存,以及使用智能指针来自动管理内存的生命周期。

std::shared_ptr

std::shared_ptr 是 C++ 标准库<memory> 头文件中定义的一个模板类,它实现了一个共享所有权的 智能指针。 std::shared_ptr 允许多个智能指针实例共享同一个对象的所有权。当最后一个指向某对 象的 std::shared_ptr 被销毁(例如离开其作用域)或被显式重置时,它所指向的对象才会被自动删 除。

std::shared_ptr 通过引用计数来管理对象的生命周期。每个 std::shared_ptr 实例内部持有一个 指向控制块的指针,这个控制块包含了所指向对象的指针以及一个引用计数。每次复制 std::shared_ptr 时,引用计数就会增加;每次销毁或重置 std::shared_ptr 时,引用计数就会减 少。当引用计数减少到 0 时, std::shared_ptr 就会自动删除它所指向的对象。

使用 std::shared_ptr 可以避免手动管理内存时可能出现的许多问题,如内存泄漏和野指针。此外, std::shared_ptr 还提供了自定义删除器和分配器的功能,以适应不同的内存管理需求。

定义std::shared_ptr对象

std::shared_ptr<类型名称> ptr1;

初始化std::shared_ptr对象

std::shared_ptr<类型名称> ptr1 = std::make_shared<类型名称>();

示例

#include <iostream>
#include <memory>

struct MyStruct
{
    MyStruct(int value) : value_(value) {}

    ~MyStruct()
    {
        std::cout << "Deleting MyStruct with value " << value_ << std::endl;
}
    int value_;
};

int main()
{

    // 创建一个指向 MyStruct 的 shared_ptr
    std::shared_ptr<MyStruct> ptr1 = std::make_shared<MyStruct>(42);

    // 复制 shared_ptr,增加引用计数
    std::shared_ptr<MyStruct> ptr2 = ptr1;

    // 输出当前引用计数
    std::cout << "Reference count: " << ptr1.use_count() << std::endl;

    // 当 ptr1 离开作用域并被销毁时,引用计数不会减少到 0,因为还有 ptr2 指向同一个对象
    {
        std::shared_ptr<MyStruct> ptr3 = ptr1;
        // 在这个作用域内,引用计数至少为 2(ptr2 和 ptr3)
    } // ptr3 离开作用域,引用计数减少 1

    // 当 ptr2 离开作用域并被销毁时,引用计数减少到 0,对象被删除
    // 输出析构函数的调用信息
return 0;
}

std::unique_ptr

std::unique_ptr 是一种独占所有权的智能指针。它不允许复制,但允许移动。这意味着在任何时候, 都只有一个 unique_ptr 可以拥有对象的所有权。当 unique_ptr 离开其作用域或被销毁时,它所指向 的对象也会被自动删除。

定义std::unique_ptr对象

std::unique_ptr<类型名称> ptr1;

初始化std::unique_ptr对象

std::unique_ptr<类型名称> ptr1 = std::make_unique<类型名称>();

示例

#include <iostream>
#include <memory>

struct MyStruct
{
    MyStruct(int value) : value_(value) {}
    ~MyStruct()
    {
        std::cout << "Deleting MyStruct with value " << value_ << std::endl;
    }
    int value_;
};

int main()
{

    // 使用 std::make_unique 创建 std::unique_ptr
    std::unique_ptr<MyStruct> ptr = std::make_unique<MyStruct>(42);

    // 输出 ptr 指向的对象的值
    std::cout << "Value: " << ptr->value_ << std::endl;

    //std::unique_ptr<MyStruct> ptr2 = ptr; // error:不能拷贝复制
    //std::unique_ptr<int> ptr3(ptr); // error:不能拷贝构造

    std::unique_ptr<MyStruct> ptr2 = std::move(ptr);//可以移动赋值
    std::unique_ptr<MyStruct> ptr3(std::move(ptr2));//可以移动构造

    //当使用移动赋值或移动构造转移对象所有权后,原指针变为悬空指针。
    ptr = NULL;
    ptr2 = NULL;

    // 当 ptr3 离开作用域时,它所指向的对象会被自动删除
    // 输出析构函数的调用信息
return 0;
}

std::weak_ptr

std::weak_ptr 是一种不控制对象生命周期的智能指针,它指向一个由 shared_ptr 管理的对象。 weak_ptr 的主要目的是为了解决 shared_ptr 之间的循环引用问题。它不会增加引用计数,因此不会 导致对象被错误地保持活动状态。这在你需要访问一个对象,但又不想拥有它的所有权时非常有用,比 如你想在对象的生命周期内保持对它的引用,但又不想阻止对象被删除。

定义std::weak_ptr对象

std::weak_ptr<类型名称> ptr1;

初始化std::weak_ptr对象

std::weak_ptr<类型名称> ptr1 = std::make_shared<类型名称>();

示例

#include <iostream>
#include <memory>

class CB;
class CA
{
public:
    CA()
    {
        std::cout << "CA()" << std::endl;
    }
    ~CA()
    {
        std::cout << "~CA()" << std::endl;
    }
    void set_ptr(std::shared_ptr<CB>& ptr)
    {
        m_ptr_b = ptr;
    }
private:
    std::shared_ptr<CB> m_ptr_b;
};

class CB
{
public:
    CB()
    {
        std::cout << "CB()" << std::endl;
    }
    ~CB()
    {
        std::cout << "~CB()" << std::endl;
    }
    void set_ptr(std::shared_ptr<CA>& ptr)
    {
        m_ptr_a = ptr;
    }
private:
    std::shared_ptr<CA> m_ptr_a;
};
int main()
{
    std::shared_ptr<CA> ptr_a(new CA());
    std::shared_ptr<CB> ptr_b(new CB());

    ptr_a->set_ptr(ptr_b);
    ptr_b->set_ptr(ptr_a);

    std::cout << ptr_a.use_count() << " " << ptr_b.use_count() << std::endl;
return 0;
}

以上内容均为作者个人见解,如有问题,还请读者斧正!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值