C++最佳实践之常用库介绍
C++的常用库包括:algorithm、chrono、iostream、future、memory、map、unordered_map、queue、regex、set、string、sstream、stdexcept、thread、vector、mutex等。熟悉这些C++库对我们开发有很大帮助,我们结合代码实践来介绍。.........
C++的常用库包括:algorithm、chrono、iostream、future、memory、map、queue、unordered_map、regex、set、string、sstream、stdexcept、thread、vector、mutex等。熟悉这些C++库对我们开发有很大帮助,我们结合代码实践来介绍。
目录
一、algorithm算法
algorithm库包括:min、max、sort、binary_search、reverse、replace等函数。部分源码如下:
template <class T>
const T& min(const T& a, const T& b);
template <class T>
const T& max(const T& a, const T& b);
template <class RandomAccessIterator>
void sort(RandomAccessIterator first, RandomAccessIterator last);
template <class ForwardIterator, class T>
bool binary_search(ForwardIterator first, ForwardIterator last, const T& value);
template <class BidirectionalIterator>
void reverse(BidirectionalIterator first, BidirectionalIterator last);
template <class ForwardIterator, class T>
void replace(ForwardIterator first, ForwardIterator last, const T& old_value, const T& new_value);
1、最小值与最大值
使用min()求两者最小值,用max()求两者最大值。示例如下:
int a = 1, b = 2;
int min = std::min(a, b);
int max = std::max(a, b);
2、排序
使用sort()进行排序,接受的参数为iterator迭代器,示例如下:
std::vector<int> array{3, 6, 1, 5, 9, 2, 8};
std::sort(array.begin(), array.end());
for (int it: array) {
printf("%d ", it);
}
3、二分查找
二分查找是基于有序数组使用二分法进行查找,如果找到返回true。示例如下:
bool result = std::binary_search(array.begin(), array.end(), 8);
4、反转
反转是把迭代器从头到尾反过来,比如一个升序数组反转后变成降序数组。示例如下:
std::reverse(array.begin(), array.end());
5、替换
替换是遍历迭代器把就内容替换为新内容,示例如下:
std::replace(array.begin(), array.end(), 6, 666);
二、chrono时钟
在chrono库提供时间单位有:时、分、秒、毫秒、微秒、纳秒。如下表所示:
chrono::hours | 时 |
chrono::minutes | 分 |
chrono::seconds | 秒 |
chrono::milliseconds | 毫秒 |
chrono::microseconds | 微秒 |
chrono::nanoseconds | 纳秒 |
使用chrono可以获取日期、当前时间,也可以计算时间差。示例如下:
using namespace std::chrono;
system_clock::time_point time_point = system_clock::now();
// 获取日期
time_t time = system_clock::to_time_t(time_point);
printf("date=%s", ctime(&time));
// 获取当前时间,单位ms
long time_millis = time_point.time_since_epoch().count() / 1000;
printf("current millisecond=%ld", time_millis);
// 计算时间差
system_clock::time_point begin = system_clock::now();
int sum;
for (int i = 0; i < 10000; ++i) {
sum += i;
}
system_clock::time_point end = system_clock::now();
duration<double> diff = duration_cast<duration<double>>(end - begin);
printf("use time=%lf", diff.count());
三、iostream输入输出流
输入输出流定义在iostream.h头文件中,内部包含ios.h、istream.h、ostream.h。而fstream.h提供打开和关闭文件的函数。
1、ios.h
ios头文件提供打开文件的模式,还有seek模式。其中打开文件模式如下表所示:
ios::app | 以追加方式打开 |
ios::ate | 文件打开后定位到文件尾 |
ios::binary | 以二进制方式打开 |
ios::in | 以输入方式打开(读) |
ios::out | 以输出方式打开(写) |
ios::nocreate | 不建立文件,文件不存在时打开失败 |
ios::noreplace | 不覆盖文件,文件存在时打开失败 |
ios::trunc | 如果文件存在,把文件长度设为0 |
seek模式包括:从头开始、当前位置和尾部开始,枚举定义如下:
enum seekdir {beg, cur, end};
2、istream.h
istream头文件提供输入流操作,源码定义如下:
// 读一个字符
basic_istream& get(char_type& c);
// 读取一行
basic_istream& getline(char_type* s, streamsize n);
// 读取指定长度的内容
basic_istream& read (char_type* s, streamsize n);
// 读模式的seek
basic_istream& seekg(off_type, ios_base::seekdir);
3、ostream.h
ostream头文件提供输出流操作,源码定义如下:
// 写一个字符
basic_ostream& put(char_type c);
// 写入指定长度的内容
basic_ostream& write(const char_type* s, streamsize n);
// 刷新
basic_ostream& flush();
// 获取当前位置
pos_type tellp();
// 绝对位置的seek
basic_ostream& seekp(pos_type);
// 指定模式的seek
basic_ostream& seekp(off_type, ios_base::seekdir);
四、future异步任务
future用于执行异步任务,等待执行结束后用get()获取结果。我们可以从packaged_task获取future,或者从async()获取future,也可以从promise获取future。示例如下:
// 从packaged_task获取future
std::packaged_task<int()> task([] { return 1; });
std::future<int> f1 = task.get_future();
std::thread t(std::move(task)); // 启动线程
// 从async()获取future
std::future<int> f2 = std::async(std::launch::async, [] { return 2; });
// 从promise获取future
std::promise<int> p;
std::future<int> f3 = p.get_future();
std::thread([&p] { p.set_value_at_thread_exit(3); }).detach();
f1.wait();
f2.wait();
f3.wait();
t.join();
printf("f1=%d, f2=%d, f3=%d\n", f1.get(), f2.get(), f3.get());
五、memory内存管理
memory头文件提供四种智能指针:shared_ptr、unique_ptr、weak_ptr和auto_ptr。其中auto_ptr已经过时。智能指针的特性对比如下:
shared_ptr | 共享指针,使用引用计数 |
unique_ptr | 单一指针,一般用在单例场景 |
weak_ptr | 弱指针,对共享指针进行观察 |
另外提供allocator分配器,使用示例如下:
std::allocator<int> allocator;
int size = 3;
// 分配内存块
int *ptr = allocator.allocate(size);
// 给每个内存地址赋值
allocator.construct(ptr, 1);
allocator.construct(ptr + 1, 2);
allocator.construct(ptr + 2, 3);
for (int i = 0; i < size; i++) {
printf("alloc=%d", *(ptr + i));
// 释放对应的内存地址
allocator.destroy(ptr + i);
}
// 释放内存块
allocator.deallocate(ptr, size);
六、map与unordered_map
C++提供map和unordered_map两种数据结构。其中map基于红黑树,unordered_map基于哈希表。优缺点对比如下:
优点 | 缺点 | |
map | 基于红黑树有序,操作时间复杂度lgn | 保存父节点和子节点,空间复杂度高 |
unordered_map | 基于哈希查找效率高 | 无序,建立哈希表耗时 |
map的操作示例如下:
std::map<int, std::string> map;
// add
map.insert(std::pair<int, std::string>(1, "ferrari"));
map.insert(std::pair<int, std::string>(2, "lanbojini"));
map.insert(std::pair<int, std::string>(3, "rollsroyce"));
map.insert(std::pair<int, std::string>(6, "benzi"));
// remove
map.erase(6);
// 遍历
for (auto it = map.begin(); it != map.end(); it++) {
printf("key=%d, value=%s", it->first, it->second.c_str());
}
// find
auto it = map.find(3);
if (it != map.end()) {
printf("found value=%s", it->second.c_str());
}
七、queue队列
queue队列是FIFO先进先出,与queue相反的是stack,属于LIFO后进先出。我们来看看队列的操作示例:
std::queue<int> queue;
// 入队
queue.push(111);
queue.push(222);
queue.push(333);
printf("queue front=%d, back=%d", queue.front(), queue.back());
// 出队
while (!queue.empty()) {
int front = queue.front();
queue.pop();
}
八、regex正则匹配
regex库是C++提供的正则匹配。示例如下:
// 匹配规则
std::regex regular(".{5},\\d{4}");
std::string str_in("hello,2022");
// 匹配结果
std::smatch result;
// 调用正则匹配
if (std::regex_match(str_in, result, regular)) {
for (int i = 0; i < result.size(); ++i) {
LOGE("match=%s", result[i].first);
}
}
九、set集合
set是不重复key的集合,保证key的唯一性。示例如下:
std::set<std::string> set;
set.insert("hello");
set.insert("world");
printf("size=%lu\n", set.size());
if (auto it = set.find("hello") != set.end()) {
printf("find result=%d\n", it);
}
set.erase("hello");
set.clear();
十、string字符串操作
字符串操作包括:拼接、删除、截取、替换、判断是否相等。使用示例如下:
std::string str("hello");
// 后面追加
str.append(" world");
// 前面插入
str.insert(0, "ok,");
// 截取子字符串
str = str.substr(3);
printf("sub str=%s\n", str.c_str());
// 空格替换为逗号
for (int i = 0; i < str.size(); ++i) {
char ch = str.at(i);
if (ch == ' ') {
str.replace(i, 1, 1, ',');
}
}
// 后面添加字符
str.push_back('!');
// 删除指定位置的字符
str.erase(str.size() - 1);
size_t pos = str.find("world");
printf("find pos=%ld\n", pos);
// 判断字符串是否相等,==属于操作符重载
if (str == "hello,world") {
// ...
}
十一、字符串格式化
在C语言中,我们可以使用sprintf()函数进行字符串格式化输出,整型用%d,浮点型用%f,长整型用%ld,字符串用%s。在Java语言中,可以使用StringFormat进行格式化。今天的主角是C++的sstream库,提供stringstream进行字符串格式化。示例如下:
// 字符串格式化
std::stringstream stream;
int a = 10;
float b = 3.5;
long c = 666;
stream << "a=" << a << ", b=" << b << ", c=" << c;
printf("stream:%s", stream.str().c_str());
十二、标准异常
C++在stdexcept库提供标准异常。基类是exception,位于exception.h,源码如下:
class exception
{
public:
exception() _NOEXCEPT {}
virtual ~exception() _NOEXCEPT;
virtual const char* what() const _NOEXCEPT;
};
继承exception的类如下表所示:
bad_exception | 破坏异常,位于exception.h |
bad_alloc | 分配异常,位于new.h |
bad_cast | 转换异常,位于typeinfo.h |
logic_error | 逻辑错误,位于stdexcept.h |
runtime_error | 运行时错误,位于stdexcept.h |
继承logic_error的类如下表所示:
out_of_range | 数组越界 |
length_error | 长度错误 |
invalid_argument | 无效参数 |
domain_error | 域错误 |
继承runtime_error的类如下表所示:
range_error | 边界错误 |
overflow_error | 内存上溢 |
underflow_error | 内存下溢 |
十三、vector容器
vector是一个容器,基于模板类,理论上可支持任意类型。与数组区别是,vector可以动态扩容。另外,vector有begin()和end()迭代器。相关操作示例如下:
std::vector<int> vector;
// 尾部压入
vector.push_back(2);
// 指定位置插入
vector.insert(vector.begin(), 1);
vector.push_back(3);
// 迭代器遍历
for (auto it = vector.begin(); it != vector.end(); it++) {
printf("val=%d\n", *it);
}
// 获取头部和尾部数值
int front = vector.front();
int back = vector.back();
// 移除首位的数值
vector.erase(vector.begin());
// 尾部弹出
vector.pop_back();
// 清空容器
vector.clear();
十四、互斥锁
C++提供的互斥锁有:lock_guard、unique_lock、shared_lock和scoped_lock。对比如下:
lock_guard | 互斥锁包装器,构造时上锁,析构时解锁 |
unique_lock | 单一锁,可手动释放锁,锁的粒度更细 |
shared_lock | 共享锁,以共享模式锁住互斥,c++14 |
scoped_lock | 作用域锁,接受多个mutex,c++17 |
十五、thread线程库
C++的线程库是对pthread的封装,可以使用join启动,也可以使用detach启动。其中,join会让主线程等待子线程执行结束;而detach是把子线程分离出来,与主线程互不影响。下面是两个线程交替打印数字,模拟多线程同步的示例:
bool flag;
std::mutex mtx;
std::condition_variable cond;
void task1() {
for (int i = 0; i < 10; ++i) {
std::unique_lock<std::mutex> lock(mtx);
cond.wait(lock, []() {return flag;});
if (i % 2 == 0) {
printf("%s, count=%d\n", __func__, i);
}
flag = !flag;
cond.notify_one();
lock.unlock();
}
}
void task2() {
for (int i = 0; i < 10; ++i) {
std::unique_lock<std::mutex> lock(mtx);
cond.wait(lock, []() {return !flag;});
if (i % 2 == 1) {
printf("%s, count=%d\n", __FUNCTION__, i);
}
flag = !flag;
cond.notify_one();
lock.unlock();
}
}
然后是分别创建启动两个打印线程,奇偶交替打印输出:
std::thread thread1(task1);
std::thread thread2(task2);
thread1.detach();
thread2.detach();
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)