模版与标准库

泛型编程
    所谓泛型编程,就是指独立于某些特定类型的编程,使得同一份代码适用于多种数据类型,从而提高代码重用性和灵活性。

    在C++中,使用模板来实现泛型编程。

模板 template
    模板用于描述那些通用的行为或概念。
    根据描述的对象不同,模板可分为:
    函数模板
    类模板   
函数模板 
    函数模板不是函数,而是用来生成函数的一种模板(机制)    
    一般格式:
 

  template<typename T1, ..., typename Tn>
    返回类型 函数名(参数列表)
    {
        函数体
    }
    例:
    template<typename T>
    void swap(T&a, T& b)
    {
        T tmp = a;
        a = b;
        b = tmp;
    }

    int a = 1;
    int b = 2;
    swap(a, b);=> 根据实参的值,推导出模板参数的类型为int, 生成具体的函数 void swap(int&,int&), 再调用这个具体生成的函数

说明:
    1、模板声明中的 typename 用于说明其后的标识符,是一个类型的名字,也可以使用关键字 class
    2、一般情况下,编译器能根据实参自动推导模板参数的具体类型
        但,如果编译器无法根据实参推导出所有的模板参数,或推导出来的模板参数与期望的不一致
        此时,需要显示的指定模板函数类型,如下:
        函数模板名<模板参数列表>(实参列表)
       

 例: 
        template<class T>
        T max(T a, T b)
        {
            return a > b ?a : b;
        }
        max(a, b)   // 自动推导
        max<int>(a,b)// 显示指定

 3、模板的声明与定义如果分开,则在声明与定义时都需要添加模板声明
        且,模板的声明与定义必须出现在同一个编译单元中。
        所以,一般的做法是把模板写在头文件中,且声明与定义一般不分开。
4、模板特化
        模板特化是 通用模板的一种特殊情况,  一般如下:
     

  template<>
        返回类型 函数名(参数列表)
        {
            函数体 
        }
        模板的特化版本,必须出现在通过版本的后面。
        例: 
        template <typename T> // 通过版本
        int mycompare(T a, T b)
        {
            if (b < a)
                return 1;

            if (a < b)
                return -1;

            return 0;
        }

        template <> // 特别版本
        int mycompare(const char *a, const char *b)
        {
            if (strcmp(a, b) > 0)
                return 1;

            if (strcmp(a, b) < 0)
                return -1;

            return 0;
        }        


类模板
    类模板不是类,而是用来生成类的一种机制
    一般格式:
   

 template<typename T1, ..., typename Tn>
    class 类名
    {
    public:
        公有方法
    private:
        私有数据/方法
    };

    说明: 
        使用类模板时,必须显示的指定模板参数

        当用类模板实例化对象时,是编译器自动使用模板生成类型,再用类型实例化对象
            模板类名<模板参数列表> 对象名;

        类模板的成员函数声明与定义如果分开,则它们必须出现在同一个编译单元中,且在类外定义成员函数时,必需要添加模板声明。   

例:
    template<typename T>
    class SeqStack
    {
    public:
        SeqStack(int n);
        ~SeqStack();
        void push(T data);
        void pop();
        T top() const;
        bool empty() const;
    private:
        T* _data;
        int _top;
        int _max;
    };

    SeqStack<int> stack1(10);
    stack1.push(1);
    stack1.push(2);
    stack1.push(3);
    while (!stack1.empty())
    {
        cout << stack1.top() << endl;
        stack1.pop();
    }
    SeqStack<std::string> stack2(5);

标准模板库 Standard Template Library - STL
    STL是C++标准库的重要组成部分
    STL由以下几大组件组成:
    容器、算法、迭代器、适配器、分配器、函数对象
    核心组件:容器、算法、迭代器

容器 Container
    容器是一些通用的类模板与算法的汇集,允许程序员简单地实现如队列、链表和栈这样的常见数据结构。有两 (C++11 前)三 (C++11 起)类容器:
    顺序容器
    关联容器
    无序关联容器(C++11 起)
    它们被设计为各自支持一组不同的操作。
    容器管理着为其元素分配的存储空间,并提供成员函数来直接访问或通过迭代器(具有类似于指针的属性的对象)访问它们。

    顺序容器
    序列容器实现能按顺序访问的数据结构。
    array           (C++11)固定大小的原位连续数组(类模板)
    vector          动态的连续数组(类模板)
    deque           双端队列(类模板)
    forward_list    (C++11 起)单链表(类模板)
    list            双链表(类模板)

    关联容器
    关联容器实现能快速查找(O(log n) 复杂度)的有序数据结构。
    set         唯一键的集合,按照键排序(类模板)
    map         键值对的集合,按照键排序,键是唯一的(类模板)
    multiset    键的集合,按照键排序(类模板)
    multimap    键值对的集合,按照键排序(类模板)


    无序关联容器 (C++11 起)
    无序关联容器提供能快速查找(平均 O(1),最坏情况 O(n) 的复杂度)的无序(散列)数据结构。
    unordered_set       (C++11 起)唯一键的集合,按照键生成散列(类模板)
    unordered_map       (C++11 起)键值对的集合,按照键生成散列,键是唯一的(类模板)
    unordered_multiset  (C++11 起)键的集合,按照键生成散列(类模板)
    unordered_multimap  (C++11 起)键值对的集合,按照键生成散列(类模板)


    容器适配器
    容器适配器为顺序容器提供了不同的接口。
    stack           适配一个容器以提供栈(LIFO 数据结构)(类模板)
    queue           适配一个容器以提供队列(FIFO 数据结构)(类模板)
    priority_queue  适配一个容器以提供优先级队列(类模板)


智能指针
unique_ptr(C++11)       拥有独有对象所有权语义的智能指针(类模板)
    独占式智能指针,同一时刻只允许一个指针指向它所管理的对象
    当unique_ptr对象销毁时,自动释放它所管理的 对象。
    unique_ptr 不可复制,但可移动。
    使用示例: 
   

std::unique_ptr<Demo> p2 = std::make_unique<Demo>(); // C++14
    p2->print();

shared_ptr(C++11)       拥有共享对象所有权语义的智能指针(类模板)
    共享式智能指针,允许多个共享式智能指针同时访问它们所管理的对象
    shared_ptr 可复制,也可移动。
    shared_ptr 内部有一个引用计数器,当拷贝 shared_ptr时,引用计数+1,当 shared_ptr 销毁时,引用计数-1
    当引用计数减到0时,自动释放它所管理的对象。
    
    使用示例:
   

std::shared_ptr<Demo> p2 = std::make_shared<Demo>();
    p2->print();

weak_ptr(C++11)         到 std::shared_ptr 所管理对象的弱引用(类模板)
    std::weak_ptr 是一种智能指针,它对被 std::shared_ptr 管理的对象存在非拥有性(「弱」)引用。
    在访问所引用的对象前必须先转换为 std::shared_ptr。
    std::weak_ptr 用来表达临时所有权的概念:
        当某个对象只有存在时才需要被访问,而且随时可能被他人删除时,可以使用 std::weak_ptr 来跟踪该对象。
        需要获得临时所有权时,则将其转换为 std::shared_ptr,此时如果原来的 std::shared_ptr 被销毁,
        则该对象的生命期将被延长至这个临时的 std::shared_ptr 同样被销毁为止。
    std::weak_ptr 的另一用法是打断 std::shared_ptr 所管理的对象组成的环状引用。
        若这种环被孤立(例如无指向环中的外部共享指针),则 shared_ptr 引用计数无法抵达零,而内存被泄露。
        能令环中的指针之一为弱指针以避免此情况。

auto_ptr(C++17 中移除)  拥有严格对象所有权语义的智能指针(类模板)
    不建议使用
线程 thread
    类 thread 表示单个执行线程。线程允许多个函数同时执行。
    线程在构造关联的线程对象时立即开始执行(等待任何OS调度延迟),从提供给作为构造函数参数的顶层函数开始。
    顶层函数的返回值将被忽略,而且若它以抛异常终止,则调用 std::terminate 。
    顶层函数可以通过 std::promise 或通过修改共享变量(可能需要同步,见 std::mutex 与 std::atomic )将其返回值或异常传递给调用方。
    std::thread 对象也可能处于不表示任何线程的状态(默认构造、被移动、 detach 或 join 后),并且执行线程可能与任何 thread 对象无关( detach 后)。
    没有两个 std::thread 对象会表示同一执行线程; 
    std::thread 不是可复制构造 (CopyConstructible) 或可复制赋值 (CopyAssignable) 的,
    尽管它可移动构造 (MoveConstructible) 且可移动赋值 (MoveAssignable) 。
    
    线程创建示例:
   

 std::thread t1(task1);      // 线程函数task1是全局函数,且没有参数
    std::thread t2(task2, n);   // 线程函数task2是全局函数, 参数为值传递
    std::thread t3(task3, std::ref(n));     // 线程函数task3是全局函数,参数为引用传递
    std::thread t4(&Demo::task4, &d, n);    // 线程函数task4是Demo类的非静态成员函数, d是Demo类的对象, 参数为值传递

    线程操作:
    join()      // 阻塞等待线程结束
    detach()    // 使线程分离,不阻塞
    注: 线程创建后, 必须以上函数二选一执行.

    线程管理:
    std中有一个嵌套的命名空间 this_thread, 其中定义了组全局函数, 可用于管理线程
        get_id()    // 用于获取当前线程的ID
        sleep_for() // 线程休眠

    注:
        编译链接 带线程的程序时,可能 需要添加编译选项 -pthread

互斥锁 mutex
    mutex 类是能用于保护共享数据免受从多个线程同时访问的同步原语。
    mutex 提供排他性非递归所有权语义:
    调用方线程从它成功调用 lock 或 try_lock 开始,到它调用 unlock 为止占有 mutex 。
    线程占有 mutex 时,所有其他线程若试图要求 mutex 的所有权,则将阻塞(对于 lock 的调用)或收到 false 返回值(对于 try_lock ).
    调用方线程在调用 lock 或 try_lock 前必须不占有 mutex 。
    若 mutex 在仍为任何线程所占有时即被销毁,或在占有 mutex 时线程终止,则行为未定义。 
    std::mutex 既不可复制亦不可移动

    说明: 
        虽然互斥锁可单独使用, 但一般会使用互斥锁包装器,进行包装, 常用包装器如下:
        lock_guard  
            lock_guard 是一种基于作用域的锁包装器, 当创建 lock_guard 对象时, 自动上锁, lock_guard 对象销毁时,自动解锁.
            典型使用方式如下:
                std::mutex m; // 创建互斥锁对象
                std::lock_guard<std::mutex> lg(m); // 进行包装,得到包装器对象 lg, 构造函数中上锁, 析构函数中解锁
        unique_lock
            unique_lock 具有 lock_guard的功能, 但功能更强大, 允许锁包装器对象存活时解锁与上锁.
             一般配合 条件变量 使用.

条件变量 condition_variable
    condition_variable 类是同步原语,能用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(条件)并通知 condition_variable 。
    有意修改变量的线程必须
        获得 std::mutex (常通过 std::lock_guard )
        在保有锁时进行修改
        在 std::condition_variable 上执行 notify_one 或 notify_all (不需要为通知保有锁)
        即使共享变量是原子的,也必须在互斥下修改它,以正确地发布修改到等待的线程。

    任何有意在 std::condition_variable 上等待的线程必须
        在与用于保护共享变量者相同的互斥上获得 std::unique_lock<std::mutex>
        执行下列之一:
        检查条件,是否为已更新或提醒它的情况
        执行 wait 、 wait_for 或 wait_until ,等待操作自动释放互斥,并悬挂线程的执行。
        condition_variable 被通知时,时限消失或虚假唤醒发生,线程被唤醒,且自动重获得互斥。之后线程应检查条件,若唤醒是虚假的,则继续等待。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值