zoukankan      html  css  js  c++  java
  • C++学习笔记之进阶编程

    进阶编程

    STL(Standard Template Library)

    • STL算法是泛型的(generic),不与任何特定数据结构和对象绑定,不必在环境类似的情况下重写代码;
    • STL算法可以量身定做,并且具有很高的效率;
    • STL可以进行扩充,可以编写自己的组件并且能与STL标准的组件进行很好的配合;

    容器(container)

    容器用于存放数据;STL的容器分为两大类:

    • 序列式容器(Sequence Containers):其中的元素都是可排序的(ordered),STL提供了vector,list,deque等序列式容器,而stack,queue,priority queue则是容器适配器
    struct Display
    {
        void operator()(int i)
        {
            cout << i << " ";
        }
    };
    
    int main()
    {
        int iArr[] = { 1, 2,3,4,5 };
    
        //序列式容器
        vector<int> iVector(iArr, iArr + 4);
        list<int> iList(iArr, iArr + 4);
        deque<int> iDeque(iArr, iArr + 4);
    
        //容器适配器
        queue<int> iQueue(iDeque);                         // 队列 先进先出
        stack<int> iStack(iDeque);                             // 栈 先进后出
        priority_queue<int> iPQueue(iArr, iArr + 4);  // 优先队列,按优先权 
    
        //序列式容器遍历——3种方法
        //序列式容器遍历   迭代器+仿函数遍历
        for_each( iVector.begin(), iVector.end(), Display() );
        cout << endl;
        //序列式容器遍历   迭代器+循环遍历    
        for (auto it = iList.begin(); it != iList.end(); it++)
        {
            cout << *it << " ";
        }
        cout << endl;
        //序列式容器遍历   for (auto i : s)
        for (auto i:iDeque)
        {
            cout <<i<< " ";
        }
    
        //容器适配器遍历
        while ( !iQueue.empty() )
        {
            cout << iQueue.front() << " ";  // 1  2 3 4
            iQueue.pop();
        }
        cout << endl;
    
        while (!iStack.empty())
        {
            cout << iStack.top() << " ";    // 4 3  2  1
            iStack.pop();
        }
        cout << endl;
    
        while (!iPQueue.empty())
        {
            cout << iPQueue.top() << " "; // 4 3 2 1
            iPQueue.pop();
        }
        cout << endl;
    
        return 0;
    }
    

    注:容器的遍历可参考 c++ vector容器遍历方式for(auto &i:s)和for(auto i:s)区别 ,仿函数参考 c++仿函数 functor

    • 关联式容器(Associative Containers):每个数据元素都是由一个键(key)和值(Value)组成,当元素被插入到容器时,按其键以某种特定规则放入适当位置;常见的STL关联容器如:set,multiset,map,multimap
    struct Display
    {
        void operator()(pair<string, double> info)
        {
            cout << info.first << ": " << info.second << endl;
        }
    };
    
    int main()
    {
        map<string, double> studentSocres;
        // []运算符作用:查找与Key匹配的元素(不存在则使用默认映射插入),返回的是Value的引用(意味着外部可以更新Value)
        studentSocres["LiMing"] = 95.0; 
        //insert逻辑:如果存在则不插入
        studentSocres.insert(pair<string, double>("zhangsan", 100.0) );    
        studentSocres.insert(map<string, double>::value_type("zhaoliu", 95.5) );
        
        for_each(studentSocres.begin(), studentSocres.end(), Display());
        cout << endl;
    
        //查找值
        map<string, double>::iterator iter;
        iter = studentSocres.find("zhaoliu");
        if (iter != studentSocres.end())
        {
            cout << "Found the score is: " << iter->second << endl;
        }
        else
        {
            cout << "Didn't find the key." << endl;
        }
    
        // 使用迭代器完成遍历查找的过程
        iter = studentSocres.begin();
        while (iter != studentSocres.end())
        {
            if (iter->second < 98.0)  // 去除不是优秀的同学
            {
                studentSocres.erase(iter++);  // 注意:迭代器失效问题
            }
            else
            {
                iter++;
            }
        }
        for_each(studentSocres.begin(), studentSocres.end(), Display());
        cout << endl;
    
        
        for (iter = studentSocres.begin(); iter != studentSocres.end(); iter++)
        {
            if (iter->second <= 98.5)
            {
                iter = studentSocres.erase(iter);  // 注意:迭代器失效问题
            }
        }
        for_each(studentSocres.begin(), studentSocres.end(), Display());
        cout << endl;
    
        studentSocres.erase(studentSocres.begin(), studentSocres.end());
        for_each(studentSocres.begin(), studentSocres.end(), Display());
        cout << endl;
    
        return 0;
    }
    

    仿函数(functor)

    • 仿函数一般不会单独使用,主要是为了搭配STL算法使用。
    • 函数指针不能满足STL对抽象性的要求,不能满足软件积木的要求,无法和STL其他组件搭配。
    • 本质就是类重载了一个operator(),创建一个行为类似函数的对象。

    C++中仿函数/函数对象,函数指针的用法

    bool MySort(int a, int b) { return a < b; }
    
    void Display(int a) {     cout << a << " "; }
    
    template<class T>
    inline bool MySortT(T const& a, T const& b) { return a < b; }
    
    template<class T>
    inline void DisplayT(T const& a) { cout << a << " "; }
    
    struct SortF
    {
        bool operator() (int a, int b) { return a < b; }
    };
    struct DisplayF
    {
        void operator() (int a) { cout << a << " "; }
    };
    
    // C++仿函数模板
    template<class T>
    struct SortTF
    {
        inline bool operator() (T const& a, T const& b) const { return a < b; }
    };
    
    template<class T>
    struct DisplayTF
    {
        inline void operator() (T const& a) const { cout << a << " ";     }
    };
    
    
    int main()
    {
        // C++方式
        int arr[] = { 4, 3, 2, 1, 7 };
        sort(arr, arr + 5, MySort);
        for_each(arr, arr + 5, Display);
        cout << endl;
    
        // C++泛型
        int arr2[] = { 4, 3, 2, 1, 7 };
        sort(arr2, arr2 + 5, MySortT<int>);
        for_each(arr2, arr2 + 5, DisplayT<int>);
        cout << endl;
    
        // C++仿函数
        int arr3[] = { 4, 3, 2, 1, 7 };
        sort(arr3, arr3 + 5, SortTF<int>() );
        for_each(arr3, arr3 + 5, DisplayTF<int>());
        cout << endl;
    
        // C++仿函数模板
        int arr4[] = { 4, 3, 2, 1, 7 };
        sort(arr4, arr4 + 5, SortF());
        for_each(arr4, arr4 + 5, DisplayF());
        cout << endl;
    
        return 0;
    }
    

    算法(algorithm)

    STL 中算法大致分为四类(包含于<algorithm>、<numeric>、<functional>):

    • 非可变序列算法:指不直接修改其所操作的容器丙容的算法;
    • 可变序列算法:指可以修改它们所操作的容器内容的算法;
    • 排序算法:包括对序列进行排序和合并的算法、搜索算法以及有序序列上的集合操作;
    • 数值算法:对容器内容进行数值计算;查找,排序和通用算法,排列组合算法,数值算法,集合算法等算法;

    最常见的算法包括:查找、排序和通用算法、排列组合算法、数值算法、集合算法等算法。

    transform

    transform函数的作用是将某操作应用于指定范围的每个元素,参考 C++学习transform函数的应用 ,示例如下:

    int main()
    {   
        // transform和lambda表达式
        int ones[] = { 1, 2, 3, 4, 5 };
        int twos[] = { 10, 20, 30, 40, 50 };
        int results[5];
        transform(ones, ones + 5, twos, results, std::plus<int>()); // 数组元素依次相加并返回
        for_each(results, results + 5, [ ](int a)->void { cout << a << endl; } ); // lambda表达式(匿名函数)
        cout << endl;
    
        return 0;
    }
    

    查找

    查找、统计相关的算法如下:

    int main()
    { 
        int arr[] = { 0, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 7, 7, 8 };
        int len = sizeof(arr) / sizeof(arr[0]);
        vector<int> iA(arr + 2, arr + 6);   // {2,3,3,4}
        
        // 统计6的个数
        cout << count(arr, arr + len, 6) << endl; 
        // 统计<7的个数, bind2nd已经启用
        cout << count_if(arr, arr + len, bind2nd(less<int>(),  7) ) << endl; 
         // 9找不到,二分法查找
        cout << binary_search(arr, arr + len, 9) << endl;  
        // 查找子序列,不存在时返回负值
        cout << *search(arr, arr + len, iA.begin(), iA.end()) << endl; 
    
        cout << endl;
    
        return 0;
    }
    

    上面使用了“arr + len”,但数组名不是指针,只是可以转换为指向其指代实体的指针,参考 C/C++中数组名退化为指针的情况
    注:binder1stbinder2nd在 c + + 11 中已弃用,在 c + + 17 中删除。

    全排列

    输入一个不存在重复字符的字符串,打印出字符串中字符的全排列,比如:
    输入: "123" 321 = 3!
    输出: 123 132 213 231 321 312

    //f(123) = 1+f(23), f(23) = 2+f(3), f(3)  = 3  递归
    void swap(char* a, char* b)
    {
        char temp = *a;
        *a = *b;
        *b = temp;
    }
    void Permutation(char* pStr, char* pPostion)
    {
        // 基准点
        if (*pPostion == '')
        {
            cout << pStr << endl;
        }
        else
        {
            for (char* pChar = pPostion; *pChar != ''; pChar++)
            {
                // 依次和后面的字符交换
                swap(*pChar, *pPostion);
    
                Permutation(pStr, pPostion + 1);
    
                // 换回来
                swap(*pChar, *pPostion);
            }
        }
    }
    
    int main()
    {
        char test[] = "132";
        Permutation(test, test);
        cout << endl;
    
        // 用STL输出全排列
        // 注意:必须要保证数组顺序,从小到大
        do
        {
            cout << test[0] << test[1] << test[2] << endl;
        } while (next_permutation(test, test + 3));
        cout << endl;
    
        char test2[] = "321";
        // 注意:必须要保证数组顺序,从大到小
        do
        {
            cout << test2[0] << test2[1] << test2[2] << endl;
        } while (prev_permutation(test2, test2 + 3));
    
        return 0;
    }
    

    迭代器(iterator)

    迭代器是一种smart pointer,用于访问顺序容器和关联容器中的元素,相当于容器和操纵容器的算法之间的中介。
    迭代器按照定义方式分成以下四种:

    • 正向迭代器:iterator
    • 常量正向迭代器:const_iterator
    • 反向迭代器:reverse_iterator
    • 常量反向迭代器:const_reverse_iteratorr
    int main()
    {
        list<int> v;
        v.push_back(3);
        v.push_back(4);
        v.push_front(2);
        v.push_front(1);  // 1, 2, 3, 4
    
        list<int>::const_iterator it;
        for (it = v.begin(); it != v.end(); it++)
        {
            //*it += 1;
            cout << *it << " ";
        }
        cout << endl;
    
        // 注意:迭代器不支持<
        //for (it = v.begin(); it < v.end(); it++)
        //{
        //    cout << *it << " ";
        //}
    
        cout <<v.front() << endl;
        v.pop_front();  // 从顶部去除
    
        list<int>::reverse_iterator it2;
        for (it2 = v.rbegin(); it2 != v.rend(); it2++)
        {
            *it2 += 1;
            cout << *it2 << " "; // 5 4 3
        }
        cout << endl;
    
        return 0;
    }
    

    容器适配器(adapter)

    • stack 堆栈:一种”先进后出”的容器,底层数据结构是使用的deque;
    #include<stack>//定义栈所需的头文件
    using namespace std;
    stack<int> s;//定义一个元素为int型的栈
    int a=10;
    s.push(a);//将a入栈
    s.pop();//出栈一个元素
    s.empty();//返回栈是否为空
    s.size();//返回栈的大小
    s.top();//返回栈顶元素
    
    • queue队列:一种”先进先出”的容器,底层数据结构是使用的deque;
    
    #include<queue>//定义队列所需的头文件
    using namespace std;
    queue<int> s;//定义一个元素为int型的栈
    int a=10;
    q.push(a);//将a队尾插入一个元素
    q.pop();//删除队头的元素
    q.empty();//返回队列是否为空,是的话返回1,不是返回0
    q.size();//返回队列的大小
    a=q.front();//返回队首元素
    a=q.back();//返回队尾元素
    
    • priority_queue 优先队列:一种特殊的队列,它能够在队列中进行排序(堆排序),底层实现结构是vector或者deque;
    priority_queue<int> pq;  // 默认是最大值优先
    //priority_queue<int, vector<int>, less<int> > pq2; //   最大值优先
    //priority_queue<int, vector<int>, greater<int> > pq3; // 最小值优先
    
    pq.push(2);
    pq.push(1);
    pq.push(3);
    pq.push(0);
    while (!pq.empty())
    {
        int top = pq.top();
        cout << " top is: " << top<< endl;
        pq.pop();
    }
    cout << endl;
    

    注:用法可参考 C++栈和队列(stack,queue,priority_queue)

    空间配置器(allocator)

    从使用的角度来看,allocator隐藏在其他组件中默默工作,不需要关心,但是从理解STL实现角度来看,它是需要首先分析的组件。 allocator的分析可以体现C++在性能和资源管理上优化思想。

    #include "jjalloc.h"
    #include <vector>
    using namespace std;
    
    int main()
    {
        int ia[5] = { 0, 1, 2, 3, 4 };
        unsigned int i;
        //为vector配置自定义的空间配置器
        vector<int, JJ::allocator<int> > iv(ia, ia + 5);
        for (i = 0; i < iv.size(); i++)
        {
            cout << iv[i] << " ";
        }
        cout << endl;
        return 0;
    }
    

    注:《STL源码剖析》侯捷,SGISTL版本的可读性较强。

    STL总结

    • STL的六大组件给软件编程带来了新的多态和复用,是现代C++语言高效的精髓;
    • 泛型和STL的学习路线很陡,建议初学者先学会基本使用和简单扩展;
    • 掌握了一定基础的情况下,可以通过进一步学习和分析源码,编写自己的组件来提升能力;

    关于Boost库

    Boost库是为C++语言标准库提供扩展的一些C++程序库的总称,由Boost社区组织开发、维护,Boost库可以与C++标准库完美共同工作,并且为其提供扩展功能。

    Boost可为大致为20多个分类:字符串和文本处理库、容器库、算法库、函数对象和高阶编程库、综合类库等等。

    具体见:https://www.boost.org/

    个人认为如果C++标准库可以满足日常开发的需求,Boost库没有学习必要。

    多线程

    线程基础

    使用C++11中Thread时注意线程安全问题,可以使用mutex等锁:

    #include <thread>
    #include <mutex>
    #include <iostream>
    using namespace std;
    
    mutex g_mutex;
    void T1()
    {
        g_mutex.lock();
        cout << "T1 Hello" << endl;
        g_mutex.unlock();
    }
    void T2(const char* str)
    {
        g_mutex.lock();
        cout << "T2 " << str << endl;
        g_mutex.unlock();
    }
    int main()
    {
        thread t1(T1);
        thread t2(T2, "Hello World");
        t1.join();
        //t2.join();
        t2.detach();
        cout << "Main Hi" << endl;
    
        return 0;
    }
    

    join()、detach()可参考
    C++11多线程join()和detach()的理解

    • join()函数是一个等待线程完成函数,主线程需要等待子线程运行结束了才可以结束;
    • detach()函数称为分离线程函数,使用detach()函数会让线程在后台运行,即说明主线程不会等待子线程运行结束才结束;

    互斥锁mutex可以配合lock_guardunique_lock使用,参考 c++11中的lock_guard和unique_lock使用浅析
    原子操作atomic的也可用于线程同步,常用于变量资源,参考 C++原子操作 atomic的使用及效率

    线程交换

    thread tW1([]()
    {
        cout << "ThreadSwap1 " << endl;
    });
    thread tW2([]()
    {
        cout << "ThreadSwap2 " << endl;
    });
    cout << "ThreadSwap1' id is " << tW1.get_id() << endl;
    cout << "ThreadSwap2' id is " << tW2.get_id() << endl;
    
    cout << "Swap after:" << endl;
    swap(tW1, tW2); 
    cout << "ThreadSwap1' id is " << tW1.get_id() << endl;
    cout << "ThreadSwap2' id is " << tW2.get_id() << endl;
    tW1.join();
    tW2.join();
    
    ThreadSwap1
    ThreadSwap2
    ThreadSwap1' id is 26136
    ThreadSwap2' id is 26612
    Swap after:
    ThreadSwap1' id is 26612
    ThreadSwap2' id is 26136
    

    线程移动

    thread tM1( []() { ; } );
    //tM1.join();
    cout << "ThreadMove1' id is " << tM1.get_id() << endl;
    cout << "Move after:" << endl;
    thread tM2 = move(tM1);
    cout << "ThreadMove2' id is " << tM2.get_id() << endl;
    cout << "ThreadMove1' id is " << tM1.get_id() << endl;
    tM2.join();
    
    ThreadMove1' id is 17940
    Move after:
    ThreadMove2' id is 17940
    ThreadMove1' id is 0
    

    推荐基本C++的书籍

    • 入门:
      《C++Primer》Stanley B.Lippman

    • 最佳实践:
      《C++高质量编程》林锐
      《Effective C++》候捷(译)
      《More Effective C++》候捷(译)
      《Effective STL》潘爱民(译)
      《The C++Programming Language》Bjarne Stroustrup

    • 深入:
      《STL源码剖析》候捷
      《COM本质论》Don Box
      《Exceptional C++》Addsion.Wesley
      《Inside the C++Object Model》Stanley B.Lippman
      《The Design and Evolution of C++》Bjarne Stroustrup

  • 相关阅读:
    关于DataGridView的数据源绑定字符串两个值得注意的问题
    .Net 第三方控件(转)
    sql语句linq语言lambda表达式对照
    InvokeRequired 属性 与Invoke方法
    .net垃圾回收机制
    const与readonly的区别
    Developer Express控件组合中的GridControl控件,如何自动显示每一行的序号
    XtraGrid控件6——2种GridColumn的属性
    如何让应用程序仅运行一个实例(c#)
    DevExpress控件之GridControl控件
  • 原文地址:https://www.cnblogs.com/timefiles/p/CppStudyNotesAdvancedProgramming.html
Copyright © 2011-2022 走看看