Loading

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 == '\0')
    {
        cout << pStr << endl;
    }
    else
    {
        for (char* pChar = pPostion; *pChar != '\0'; 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

posted @ 2021-06-10 22:55  二次元攻城狮  阅读(312)  评论(0编辑  收藏  举报