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(),创建一个行为类似函数的对象。
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++中数组名退化为指针的情况。
注:binder1st、binder2nd在 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多个分类:字符串和文本处理库、容器库、算法库、函数对象和高阶编程库、综合类库等等。
个人认为如果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_guard、unique_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