zoukankan      html  css  js  c++  java
  • 常用C++新特性

    摘抄于微信公众号《程序喵大人》:https://mp.weixin.qq.com/s/f7mHeiHdt7ltyAN-w3mbkQ

    auto类型推导

    auto可以让编译器在编译器就推导出变量的类型,看代码:

    auto a = 10; // 10是int型,可以自动推导出a是int
    int i = 10;auto b = i; // b是int型
    auto d = 2.0; // d是double型auto f = []() { // f是啥类型?直接用auto就行 return std::string("d");}

    利用auto可以通过=右边的类型推导出变量的类型。

    什么时候使用auto呢?简单类型其实没必要使用auto,然而某些复杂类型就有必要使用auto,比如lambda表达式的类型,async函数的类型等,例如:

    auto func = [&] {        cout << "xxx";}; // 对于func你难道不使用auto吗,反正我是不关心lambda表达式究竟是什么类型。auto asyncfunc = std::async(std::launch::async, func);

     

    智能指针

    C++11新特性中主要有两种智能指针std::shared_ptr和std::unique_ptr。

    那什么时候使用std::shared_ptr,什么时候使用std::unique_ptr呢?

    • 当所有权不明晰的情况,有可能多个对象共同管理同一块内存时,要使用std::shared_ptr;

    • 而std::unique_ptr强调的是独占,同一时刻只能有一个对象占用这块内存,不支持多个对象共同管理同一块内存。

    两类智能指针使用方式类似,拿std::unique_ptr举例:

    using namespace std;
    struct A {   ~A() {       cout << "A delete" << endl;   }   void Print() {       cout << "A" << endl;   }};

    int main() {   auto ptr = std::unique_ptr<A>(new A);   auto tptr = std::make_unique<A>(); // error, c++11还不行,需要c++14   std::unique_ptr<A> tem = ptr; // error, unique_ptr不允许移动,编译失败   ptr->Print();   return 0;}

     

    std::lock相关

    C++11提供了两种锁封装,通过RAII方式可动态的释放锁资源,防止编码失误导致始终持有锁。

    这两种封装是std::lock_guard和std::unique_lock,使用方式类似,看下面的代码:

    #include <iostream>#include <mutex>#include <thread>#include <chrono>
    using namespace std;std::mutex mutex_;
    int main() {   auto func1 = [](int k) {       // std::lock_guard<std::mutex> lock(mutex_);       std::unique_lock<std::mutex> lock(mutex_);       for (int i = 0; i < k; ++i) {           cout << i << " ";      }       cout << endl;  };   std::thread threads[5];   for (int i = 0; i < 5; ++i) {       threads[i] = std::thread(func1, 200);  }   for (auto& th : threads) {       th.join();  }   return 0;}

    普通情况下建议使用std::lock_guard,因为std::lock_guard更加轻量级,但如果用在条件变量的wait中环境中,必须使用std::unique_lock。

    条件变量

    条件变量是C++11引入的一种同步机制,它可以阻塞一个线程或多个线程,直到有线程通知或者超时才会唤醒正在阻塞的线程,条件变量需要和锁配合使用,这里的锁就是上面介绍的std::unique_lock。

    这里使用条件变量实现一个CountDownLatch:

    class CountDownLatch {   public:    explicit CountDownLatch(uint32_t count) : count_(count);
        void CountDown() {        std::unique_lock<std::mutex> lock(mutex_);        --count_;        if (count_ == 0) {            cv_.notify_all();        }    }
        void Await(uint32_t time_ms = 0) {        std::unique_lock<std::mutex> lock(mutex_);        while (count_ > 0) {            if (time_ms > 0) {                cv_.wait_for(lock, std::chrono::milliseconds(time_ms));            } else {                cv_.wait(lock);            }        }    }
        uint32_t GetCount() const {        std::unique_lock<std::mutex> lock(mutex_);      return count_;    }
       private:    std::condition_variable cv_;    mutable std::mutex mutex_;    uint32_t count_ = 0;};

    条件变量还有几个坑,可以看这篇文章:《使用条件变量的坑你知道吗

    原子操作

    C++11提供了原子类型std::atomic,用于原子操作,使用这种方式既可以保证线程安全,也不需要使用锁来进行临界区保护,对一些普通变量来说尤其方便,看代码:

    std::atomic<int> atomicInt;atomicInt++;atomicInt--;atomicInt.store(2);int value = atomicInt.load();

    多线程

    什么是多线程这里就不过多介绍,新特性关于多线程最主要的就是std::thread的使用,它的使用也很简单,看代码:

    #include <iostream>#include <thread>
    using namespace std;
    int main() {   auto func = []() {       for (int i = 0; i < 10; ++i) {           cout << i << " ";      }       cout << endl;  };   std::thread t(func);   if (t.joinable()) {       t.detach();  }   auto func1 = [](int k) {       for (int i = 0; i < k; ++i) {           cout << i << " ";      }       cout << endl;  };   std::thread tt(func1, 20);   if (tt.joinable()) { // 检查线程可否被join       tt.join();  }   return 0;}

    这里记住,std::thread在其对象生命周期结束时必须要调用join()或者detach(),否则程序会terminate(),这个问题在C++20中的std::jthread得到解决,但是C++20现在多数编译器还没有完全支持所有特性,先暂时了解下即可,项目中没必要着急使用。

    左值右值移动语义相关

    大家可能都听说过左值右值,但可能会有部分读者还没有搞清楚这些概念。这里解惑下:

    关于左值和右值,有两种方式理解:

    概念1:

    左值:可以放到等号左边的东西叫左值。

    右值:不可以放到等号左边的东西就叫右值。

    概念2:

    左值:可以取地址并且有名字的东西就是左值。

    右值:不能取地址的没有名字的东西就是右值。

    举例来说:

    int a = b + cint d = 4; // d是左值,4作为普通字面量,是右值

    a是左值,有变量名,可以取地址,也可以放到等号左边, 表达式b+c的返回值是右值,没有名字且不能取地址,&(b+c)不能通过编译,而且也不能放到等号左边。

    左值一般有:

    • 函数名和变量名

    • 返回左值引用的函数调用

    • 前置自增自减表达式++i、--i

    • 由赋值表达式或赋值运算符连接的表达式(a=b, a += b等)

    • 解引用表达式*p

    • 字符串字面值"abcd"

    介绍右值前需要先介绍两个概念:纯右值和将亡值。

    运算表达式产生的临时变量、不和对象关联的原始字面量、非引用返回的临时变量、lambda表达式等都是纯右值。例如:

    • 除字符串字面值外的字面值

    • 返回非引用类型的函数调用

    • 后置自增自减表达式i++、i--

    • 算术表达式(a+b, a*b, a&&b, a==b等)

    • 取地址表达式等(&a)

    而将亡值是指C++11新增的和右值引用相关的表达式,通常指将要被移动的对象、T&&函数的返回值、std::move函数的返回值、转换为T&&类型转换函数的返回值,将亡值可以理解为即将要销毁的值,通过“盗取”其它变量内存空间方式获取的值,在确保其它变量不再被使用或者即将被销毁时,可以避免内存空间的释放和分配,延长变量值的生命周期,常用来完成移动构造或者移动赋值的特殊任务。例如:

    class A {    xxx;};A a;auto c = std::move(a); // c是将亡值auto d = static_cast<A&&>(a); // d是将亡值

    这块的概念太多了,涉及很多知识点,这里不太展开介绍了,具体可以看这篇文章:《左值引用、右值引用、移动语义、完美转发,你知道的不知道的都在这里

     

    std::function和lambda表达式

    这两个可以说是我最常用的特性,使用它们会让函数的调用相当方便。使用std::function可以完全替代以前那种繁琐的函数指针形式。

    还可以结合std::bind一起使用,直接看一段示例代码:

    std::function<void(int)> f; // 这里表示function的对象f的参数是int,返回值是void#include <functional>#include <iostream>
    struct Foo {   Foo(int num) : num_(num) {}   void print_add(int i) const { std::cout << num_ + i << ' '; }   int num_;};
    void print_num(int i) { std::cout << i << ' '; }
    struct PrintNum {   void operator()(int i) const { std::cout << i << ' '; }};
    int main() {   // 存储自由函数   std::function<void(int)> f_display = print_num;   f_display(-9);
       // 存储 lambda   std::function<void()> f_display_42 = []() { print_num(42); };   f_display_42();
       // 存储到 std::bind 调用的结果   std::function<void()> f_display_31337 = std::bind(print_num, 31337);   f_display_31337();
       // 存储到成员函数的调用   std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;   const Foo foo(314159);   f_add_display(foo, 1);   f_add_display(314159, 1);
       // 存储到数据成员访问器的调用   std::function<int(Foo const&)> f_num = &Foo::num_;   std::cout << "num_: " << f_num(foo) << ' ';
       // 存储到成员函数及对象的调用   using std::placeholders::_1;   std::function<void(int)> f_add_display2 = std::bind(&Foo::print_add, foo, _1);   f_add_display2(2);
       // 存储到成员函数和对象指针的调用   std::function<void(int)> f_add_display3 = std::bind(&Foo::print_add, &foo, _1);   f_add_display3(3);
       // 存储到函数对象的调用   std::function<void(int)> f_display_obj = PrintNum();   f_display_obj(18);}

    从上面可以看到std::function的使用方法,当给std::function填入合适的参数表和返回值后,它就变成了可以容纳所有这一类调用方式的函数封装器。std::function还可以用作回调函数,或者在C++里如果需要使用回调那就一定要使用std::function,特别方便,这方面的使用方式大家可以读下我之前的文章《搞定c++11新特性std::function和lambda表达式

    lambda表达式可以说是C++11引入的最重要的特性之一,它定义了一个匿名函数,可以捕获一定范围的变量在函数内部使用,一般有如下语法形式:

    auto func = [capture] (params) opt -> ret { func_body; };

    其中func是可以当作lambda表达式的名字,作为一个函数使用,capture是捕获列表,params是参数表,opt是函数选项(mutable之类), ret是返回值类型,func_body是函数体。

    看下面这段使用lambda表达式的示例吧:

    auto func1 = [](int a) -> int { return a + 1; }; auto func2 = [](int a) { return a + 2; }; cout << func1(1) << " " << func2(2) << endl;

    std::function和std::bind使得我们平时编程过程中封装函数更加的方便,而lambda表达式将这种方便发挥到了极致,可以在需要的时间就地定义匿名函数,不再需要定义类或者函数等,在自定义STL规则时候也非常方便,让代码更简洁,更灵活,提高开发效率。

    std::file_system

    C++17正式将file_system纳入标准中,提供了关于文件的大多数功能,基本上应有尽有,这里简单举几个例子:

    namespace fs = std::filesystem;fs::create_directory(dir_path);fs::copy_file(src, dst, fs::copy_options::skip_existing);fs::exists(filename);fs::current_path(err_code);

    file_system之前,想拷贝个文件、获取文件信息等都需要使用好多C语言API搭配使用才能完成需求,而有了file_system,一切都变得相当简单。file_system是C++17才引入的新功能,但其实在C++14中就可以使用了,只是file_system在std::experimental空间下。

    std::chrono

    chrono很强大,也是我常用的功能,平时的打印函数耗时,休眠某段时间等,我都是使用chrono。

    在C++11中引入了duration、time_point和clocks,在C++20中还进一步支持了日期和时区。这里简要介绍下C++11中的这几个新特性。

    duration

    std::chrono::duration表示一段时间,常见的单位有s、ms等,示例代码:

    // 拿休眠一段时间举例,这里表示休眠100msstd::this_thread::sleep_for(std::chrono::milliseconds(100));

    sleep_for里面其实就是std::chrono::duration,表示一段时间,实际是这样:

    typedef duration<int64_t, milli> milliseconds;typedef duration<int64_t> seconds;

    duration具体模板如下:

    template <class Rep, class Period = ratio<1> > class duration;

    Rep表示一种数值类型,用来表示Period的数量,比如int、float、double,Period是ratio类型,用来表示【用秒表示的时间单位】比如second,常用的duration已经定义好了,在std::chrono::duration下:

    • ratio<3600, 1>:hours
    • ratio<60, 1>:minutes
    • ratio<1, 1>:seconds
    • ratio<1, 1000>:microseconds
    • ratio<1, 1000000>:microseconds
    • ratio<1, 1000000000>:nanosecons

    ratio的具体模板如下:

    template <intmax_t N, intmax_t D = 1> class ratio;

    N代表分子,D代表分母,所以ratio表示一个分数,我们可以自定义Period,比如ratio<2, 1>表示单位时间是2秒。

    time_point

    表示一个具体时间点,如2020年5月10日10点10分10秒,拿获取当前时间举例:

    std::chrono::time_point<std::chrono::high_resolution_clock> Now() {   return std::chrono::high_resolution_clock::now();}// std::chrono::high_resolution_clock为高精度时钟,下面会提到

    clocks

    时钟,chrono里面提供了三种时钟:

    • steady_clock

    • system_clock

    • high_resolution_clock

    steady_clock

    稳定的时间间隔,表示相对时间,相对于系统开机启动的时间,无论系统时间如何被更改,后一次调用now()肯定比前一次调用now()的数值大,可用于计时。

    system_clock

    表示当前的系统时钟,可以用于获取当前时间:

    int main() {   using std::chrono::system_clock;   system_clock::time_point today = system_clock::now();   std::time_t tt = system_clock::to_time_t(today);   std::cout << "today is: " << ctime(&tt);   return 0;}// today is: Sun May 10 09:48:36 2020

    high_resolution_clock

    high_resolution_clock表示系统可用的最高精度的时钟,实际上就是system_clock或者steady_clock其中一种的定义,官方没有说明具体是哪个,不同系统可能不一样,我之前看gcc chrono源码中high_resolution_clock是steady_clock的typedef




    长风破浪会有时,直挂云帆济沧海!
    可通过下方链接找到博主
    https://www.cnblogs.com/judes/p/10875138.html
  • 相关阅读:
    004-Shell 基本运算符、算术运算符、关系运算符、布尔运算符、辑运算符、字符串运算符、文件测试运算符
    003-shell 传递参数
    002-shell变量定义、使用、字符串替换,分隔,搜索、数组、注释
    【Java】验证码识别解决方案
    bufferedimage 转换成 inputstream并保存文件
    遍历json对象---Java
    【网络爬虫】Httpclient4.X中使用HTTPS的方法采集12306网站
    httpclient获取cookies
    驾考宝典排行榜之爬虫接口解决方案
    java去除字符串中的空格、回车、换行符、制表符
  • 原文地址:https://www.cnblogs.com/judes/p/14923254.html
Copyright © 2011-2022 走看看