C++最佳实践之常用库介绍

C++的常用库包括:algorithm、chrono、iostream、future、memory、map、queue、unordered_map、regex、set、string、sstream、stdexcept、thread、vector、mutex等。熟悉这些C++库对我们开发有很大帮助,我们结合代码实践来介绍。

目录

一、algorithm算法

1、最小值与最大值

2、排序

3、二分查找

4、反转

5、替换

二、chrono时钟

三、iostream输入输出流

1、ios.h

2、istream.h

3、ostream.h

四、future异步任务

五、memory内存管理

六、map与unordered_map

七、queue队列

八、regex正则匹配

九、set集合

十、string字符串操作

十一、字符串格式化

十二、标准异常

十三、vector容器

十四、互斥锁

十五、thread线程库


一、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();

猜你喜欢

转载自blog.csdn.net/u011686167/article/details/125626748
今日推荐