c++11新特性总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/byxdaz/article/details/88603387

1、类型与变量相关

1.1、nullptr
C++11 引入了 nullptr 关键字,专门用来区分空指针、0。
在某种意义上来说,传统 C++ 会把 NULL、0 视为同一种东西,这取决于编译器如何定义 NULL,有些编译器会将 NULL 定义为 ((void*)0),有些则会直接将其定义为 0。
C++ 不允许直接将 void * 隐式转换到其他类型,但如果 NULL 被定义为 ((void*)0),那么当编译char *ch = NULL;时,NULL 只好被定义为 0。

1.2、auto
auto并没有让C++成为弱类型语言,也没有弱化变量什么,只是使用auto的时候,编译器根据上下文情况,确定auto变量的真正类型。auto作为函数返回值时,只能用于定义函数,不能用于声明函数。
auto AddTest(int a, int b) 
{
    return a + b;
}

int main()
{
    auto index = 10;
    auto str = "abc";
    auto ret = AddTest(1,2);
    std::cout << "index:" << index << std::endl;
    std::cout << "str:" << str << std::endl;
    std::cout << "res:" << ret << std::endl;
}

1.3、constexpr

近似const, 可以修饰变量,也可以修饰函数,

修饰变量如:

const int global = 100;

int main () {

int temp = 100;

constexpr int a = 1; //right

constexpr int b = global; //right

constexpr int c = temp; //wrong

}

既可以赋值字面常量也可以赋值以const变量


重点:constexpr修饰的函数,生效于编译时而不是运行时, 重点应用于修饰函数使其在编译期大幅度被解释
被constexpr修饰的函数,无论是普通函数,还是类成员函数,必须是编译器可计算得到结果,即字面常量,不可是运行时才能获取的内容

1.4、using取代typedef

typedef double db; //c99

using db = double; //c++11

typedef void(*function)(int, int);//c99,函数指针类型定义

using function = void(*)(int, int);//c++11,函数指针类型定义

using kvpairs = std::map<std::string, std::string>; //c++11

using CompareOperator = std::function<int (kvpairs &, kvpairs &)>; //c++11

using query_record = std::tuple<time_t, std::string>; //c++11

template<class T> using twins = std::pair<T, T>; //更广泛的还可以用于模板

1.5、std::chrono时间相关

比以前的时间方便了许多:

std::chrono::duration<double> duration //时间间隔

std::this_thread::sleep_for(duration); //sleep

LOG(INFO) << "duration is " << duration.count() << std::endl;

std::chrono::microseconds  //微秒

std::chrono::seconds //秒

end = std::chrono::system_clock::now(); //获取当前时间

1.6、正则表达式std::regex
https://blog.csdn.net/l357630798/article/details/78235307
1.7、for循环语法
习惯C#或java的同事之前使用C++的时候曾吐槽C++ for循环没有想C#那样foreach的用法,是的,在C++11之前,标准C++是无法做到的。熟悉boost库读者可能知道boost里面有foreach的宏定义BOOST_FOREACH,但个人觉得看起并不是那么美观。
int main()
{
    int numbers[] = { 1,2,3,4,5 };
    std::cout << "numbers:" << std::endl;
    for (auto number : numbers)
    {
        std::cout << number << std::endl;
    }
}

2、容器
2.1、std::array
std::array跟数组并没有太大区别,对于多维数据使用std::array,个人反而有些不是很习惯吧。
std::array相对于数组,增加了迭代器等函数(接口定义可参考C++官方文档)。
#include <array>
int main()
{
    std::array<int, 4> arrayDemo = { 1,2,3,4 };
    std::cout << "arrayDemo:" << std::endl;
    for (auto itor : arrayDemo)
    {
        std::cout << itor << std::endl;
    }
    int arrayDemoSize = sizeof(arrayDemo);
    std::cout << "arrayDemo size:" << arrayDemoSize << std::endl;
    return 0;
}

2.2、std::forward_list
std::forward_list为从++新增的线性表,与list区别在于它是单向链表。我们在学习数据结构的时候都知道,链表在对数据进行插入和删除是比顺序存储的线性表有优势,因此在插入和删除操作频繁的应用场景中,使用list和forward_list比使用array、vector和deque效率要高很多。
2.3、std::unordered_map
std::unordered_map与std::map用法基本差不多,但STL在内部实现上有很大不同,std::map使用的数据结构为二叉树,而std::unordered_map内部是哈希表的实现方式,哈希map理论上查找效率为O(1)。但在存储效率上,哈希map需要增加哈希表的内存开销。
#include <iostream>
#include <string>
#include <unordered_map>
int main()
{
    std::unordered_map<std::string, std::string> mymap =
    {
        { "house","maison" },
        { "apple","pomme" },
        { "tree","arbre" },
        { "book","livre" },
        { "door","porte" },
        { "grapefruit","pamplemousse" }
    };
    unsigned n = mymap.bucket_count(); //哈希索引的Bucket 数量
    std::cout << "mymap has " << n << " buckets.\n";
    for (unsigned i = 0; i<n; ++i) 
    {
        std::cout << "bucket #" << i << " contains: ";
        for (auto it = mymap.begin(i); it != mymap.end(i); ++it)
            std::cout << "[" << it->first << ":" << it->second << "] ";
        std::cout << "\n";
    }
    return 0;
}

2.4、std::unordered_set
std::unordered_set的数据存储结构也是哈希表的方式结构,除此之外,std::unordered_set在插入时不会自动排序,这都是std::set表现不同的地方。
#include <iostream>
#include <string>
#include <unordered_set>
#include <set>
int main()
{
    std::unordered_set<int> unorder_set;
    unorder_set.insert(7);
    unorder_set.insert(5);
    unorder_set.insert(3);
    unorder_set.insert(4);
    unorder_set.insert(6);
    std::cout << "unorder_set:" << std::endl;
    for (auto itor : unorder_set)
    {
        std::cout << itor << std::endl;
    }

    std::set<int> set;
    set.insert(7);
    set.insert(5);
    set.insert(3);
    set.insert(4);
    set.insert(6);
    std::cout << "set:" << std::endl;
    for (auto itor : set)
    {
        std::cout << itor << std::endl;
    }
}

2.5、tuple多元数组
#include <iostream>
#include <tuple>
#include<vector>

using std::cout ;
using std::endl ;
using std::tuple;
using std::vector;
using std::string;
using std::get;
using std::make_tuple ;


auto handleCmd()
{
    auto t1 = make_tuple(1, "a1", "b1", "c1");
    cout << get<0>(t1) << " ";
    cout << get<1>(t1) << " ";
    cout << get<2>(t1) << " ";
    cout << get<3>(t1) << " ";
    cout << endl;

    vector<tuple<int, string, string, string> > tv;

    tv.push_back(make_tuple(1, "a1", "b1", "c1"));
    tv.push_back(make_tuple(2, "a2", "b2", "c2"));

    vector< tuple<int, string, string ,string> >::iterator itr;
    for (itr=tv.begin(); itr!=tv.end(); itr++)
    {
        cout << get<0>(*itr) << " ";
        cout << get<1>(*itr) << " ";
        cout << get<2>(*itr) << " ";
        cout << get<3>(*itr) << endl;
    }

    return t1;
}

3、对于类

3.1、构造函数

3.1.1、控制构造函数

1、default关键字生成默认构造函数和析构函数
default的默认构造方式可生成:默认无参数的构造函数、拷贝构造函数、赋值构造函数
析构函数同样可以由default默认构造
2、delete关键字禁止拷贝构造、禁止赋值构造、禁止自定义参数的构造函数
注意析构函数不可由delete修饰

c++11以前的方式,是把需要禁止的构造函数,放在private里使外部无法调用;

c++11风格的禁止构造的noncopyable基类实现如下,禁止了拷贝构造和赋值构造:
class noncopyable {
protected:
    constexpr noncopyable() = default;
    ~noncopyable() = default;
    noncopyable(const noncopyable &) = delete;
    noncopyable &operator= (const noncopyable &) = delete;
};
3.2、委托构造函数
一个构造函数,使用自己的参数,传递给其他构造函数去构造,作为自己的构造函数实现,
如下例,后面两个构造函数,均传递参数,委托给第一个构造函数去实现
struct A {
        bool a_;
        char b_;
        int c_;
        float d_;
        double e_;
        A(bool a, char b, int c, float d, double e): a_(a), b_(b), c_(c), d_(d), e_(e) {}
        //construct reuse
        A (int c): A(true, 'b', c, 1.1, 1000.1) {}
        A (double e): A (false, 'a', 0, 0.1, e) {}
    };
 
    A o1(10);
    LOG(INFO) << "a: " << o1.a_ << ", b: " << o1.b_ << ", c: " << o1.c_ << ", d: " << o1.d_ << ", e: " << o1.e_;
    A o2(5.5);
    LOG(INFO) << "a: " << o2.a_ << ", b: " << o2.b_ << ", c: " << o2.c_ << ", d: " << o2.d_ << ", e: " << o2.e_;

3.3、移动构造函数:
std::move函数可以以非常简单的方式将左值引用转换为右值引用。C++ 标准库使用比如vector::push_back 等这类函数时,会对参数的对象进行复制,连数据也会复制.这就会造成对象内存的额外创建, 本来原意是想把参数push_back进去就行了,通过std::move,可以避免不必要的拷贝操作。
std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝所以可以提高利用效率,改善性能.。
对指针类型的标准库对象并不需要这么做
#include <iostream>
#include <utility>
#include <vector>
#include <string>
int main()
{
    std::string str = "Hello";
    std::vector<std::string> v;
    //调用常规的拷贝构造函数,新建字符数组,拷贝数据
    v.push_back(str);
    std::cout << "After copy, str is \"" << str << "\"\n";
    //调用移动构造函数,掏空str,掏空后,最好不要使用str
    v.push_back(std::move(str));
    std::cout << "After move, str is \"" << str << "\"\n";
    std::cout << "The contents of the vector are \"" << v[0]
                                         << "\", \"" << v[1] << "\"\n";
}
输出:
After copy, str is "Hello"
After move, str is ""
The contents of the vector are "Hello", "Hello"

3.4、override和final
作用于虚函数,更多的作用是:显式的标识是否应该多态继承或不应该。
继承:子类的虚函数多态实现要加override显式的表明,不让子类多态实现的虚函数也要记得加入final;

1、override:子类用override修饰其虚函数,表示要多态继承基类的虚函数。不可以修饰非虚函数

举一个rocksdb的merge运算符重载的例子:


class ProcessMerge : public rocksdb::MergeOperator {
public:
    virtual bool FullMergeV2 (const MergeOperationInput &merge_in,
                              MergeOperationOutput *merge_out) const override {
        merge_out->new_value.clear();
        if (merge_in.existing_value != nullptr) {
            merge_out->new_value.assign(merge_in.existing_value->data(), merge_in.existing_value->size());
        }
 
        for (const rocksdb::Slice& m : merge_in.operand_list) {
            merge_out->new_value.append("|");
            merge_out->new_value.append(m.data(), m.size());
        }
 
        return true;
    }
 
    const char* Name() const override { return "ProcessMerge"; }
};

2、final:基类用final修饰其虚函数,意外其子类不可以多态继承该虚函数


class father {
    public:
        int a_;
        int GetA() {return a_;}
        virtual void SetA(int a) {
            a_ = a;
            LOG(INFO) << "father modify a to " << a_;
        }
        //add keyword final to avoid non-anticipated inherit in compling but not errored in running
        //virtual void SetA(int a) final {a_ = a;}
    public:
        father(int a):a_(a) {}
    };
 
    class Son: public father {
        int b_;
    public:
        Son(int a, int b):father(a), b_(b) {}
        //add keyword override to avoid the error in compling but not errored in running.(eg. 'int SetA(double a){...} override' woule be errored by compiler)
        virtual void SetA(int a) override {
            a_ = a;
            LOG(INFO) << "son modify a to " << a_;
        }
        //virtual void SetA(double a) override {a_ = a;}
    };
如father基类的SetA实现为"virtual void SetA(int a) final {a_ = a;}",则子类Son再多态继承实现SetA方法就会报错了。

4、lambda、bind、function
4.1、直接lambda表达式
lamda表达式不仅仅是一个语法新特性,对于没有用过java或C#lamda表达式读者,C++11的lamda表达式在一定程度上还冲击着你对传统C++编程的思维和想法。
int main()
{
    auto add= [](int a, int b)->int{
        return a + b;
    };
    int ret = add(1,2);
    std::cout << "ret:" << ret << std::endl;
    return 0;
}
 解释:
    第3至5行为lamda表达式的定义部分
    []:中括号用于控制main函数与内lamda表达式之前的变量在lamda表达式中的访问形式;
    (int a,int b):为函数的形参
    ->int:lamda表达式函数的返回值定义
    {}:大括号内为lamda表达式的函数体。

4.2、c++11风格的函数指针std::function & std::bind


    int func1 (int a, int b) {
        return a + b;
    }
 
    auto a = 1, b = 2;
    std::function<int (int, int)> modify_add0(func1);
        LOG(INFO) << "directly assign function: " << modify_add0(a, b);
通过指定返回值、参数列表、绑定的函数和函数名,定义一个函数(指针)modify_add0


绑定的函数,可以是普通函数,也可以是类成员函数,同时指定:

    class ca {
    public:
        bool func(int a) {
            LOG(INFO) << "aaa: " << a;
        }
    };
 
    ca o;
    std::function<bool (int)> f = std::bind(&ca::func, o, std::placeholders::_1);
    f(1);
原先只有在boost出现且极为受限的函数占位符,也加入到了标准库,即std::placeholders,传递自定义参数
绑定类成员函数时,需要配合使用std:bind。

bind和placeholders,同样可以用于普通函数:

int func1 (int a, int b) {
    b = a + a + a;
    return a + b;
}
 
auto a = 1, b = 2;
auto auto1 = std::bind(func1, std::placeholders::_1, std::placeholders::_2);
LOG(INFO) << "directly run auto: " << auto1(a, b);

5、动态指针
5.1、std::unique_ptr
功能基本对应boost的scoped_ptr,或之前stl的auto_ptr,生命周期随构造者,reset自动析构再重新构造,get判断是否有效、支持放在容器内;

5.2、std::shared_ptr
功能对于boost的shared_ptr,可以有多个持有者的共享指针,即所谓引用计数型指针,直到最后一个持有者delete释放时,其指向的资源才会真正被释放。
典型应用案例:如对同一个全局无锁队列对象由shared_ptr封装,多线程的多个持有者均持有对其的引用。直到全部线程都释放掉对其的引用时,该无锁队列对象才会被最终销毁。
也就是shared_ptr适合用于管理“全局动态资源”。
std::make_shared封装了new方法,boost::make_shared之前的原则是既然释放资源delete由智能指针负责,那么应该把new封装起来,否则会让人觉得自己调用了new,但没有调用delete,似乎与谁申请,谁释放的原则不符。

5.3、std::weak_ptr
std::weak_ptr网上很多人说其实是为了解决std::shared_ptr在相互引用的情况下出现的问题而存在的,C++官网对这个只能指针的解释也不多,那就先甭管那么多了,让我们暂时完全接受这个观点。
std::weak_ptr有什么特点呢?与std::shared_ptr最大的差别是在赋值时,不会引起智能指针计数增加。lock返回一个指向共享对象的shared_ptr。与任何其它shared_ptr类似,只要此shared_ptr存在,它所指向的底层对象也就会一直存在。

6、多线程与互斥同步
6.1、std::thread
std::thread为C++11的线程类,使用方法和boost接口一样,非常方便,同时,C++11的std::thread解决了boost::thread中构成参数限制的问题,我想着都是得益于C++11的可变参数的设计风格。
#include <thread>
void threadfun1()
{
    std::cout << "threadfun1 - 1\r\n" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "threadfun1 - 2" << std::endl;
}

void threadfun2(int iParam, std::string sParam)
{
    std::cout << "threadfun2 - 1" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(5));
    std::cout << "threadfun2 - 2" << std::endl;
}

int main()
{
    std::thread t1(threadfun1);
    std::thread t2(threadfun2, 10, "abc");
    t1.join();
    std::cout << "join" << std::endl;
    t2.detach();
    std::cout << "detach" << std::endl;
}

6.2、原子变量
std::atomic<XXX>
用于多线程资源互斥操作,属c++11重大提升,多线程原子操作简单了许多。
从功能上看,简单地说,原子数据类型不会发生数据竞争,能直接用在多线程中而不必我们用户对其进行添加互斥资源锁的类型。从实现上,大家可以理解为这些原子类型内部自己加了锁。
#include <thread>
#include <atomic>
#include <stdio.h>
std::atomic_bool bIsReady = false;
std::atomic_int iCount = 100;
void threadfun1()
{
    if (!bIsReady) {
        std::this_thread::yield();
    }
    while (iCount > 0)
    {
        printf("iCount:%d\r\n", iCount--);
    }
}

int main()
{
    std::atomic_bool b;
    std::list<std::thread> lstThread;
    for (int i = 0; i < 10; ++i)
    {
        lstThread.push_back(std::thread(threadfun1));
    }
    for (auto& th : lstThread)
    {
        th.join();
    }
}

6.3、std::condition_variable
C++11中的std::condition_variable就像Linux下使用pthread_cond_wait和pthread_cond_signal一样,可以让线程休眠,直到别唤醒,现在在从新执行。线程等待在多线程编程中使用非常频繁,经常需要等待一些异步执行的条件的返回结果。
// condition_variable example
#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void print_id(int id) {
    std::unique_lock<std::mutex> lck(mtx);
    while (!ready) cv.wait(lck);
    // ...
    std::cout << "thread " << id << '\n';
}

void go() {
    std::unique_lock<std::mutex> lck(mtx);
    ready = true;
    cv.notify_all();
}

int main()
{
    std::thread threads[10];
    // spawn 10 threads:
    for (int i = 0; i<10; ++i)
        threads[i] = std::thread(print_id, i);

    std::cout << "10 threads ready to race...\n";
    go();                       // go!

    for (auto& th : threads) th.join();

    return 0;
}

猜你喜欢

转载自blog.csdn.net/byxdaz/article/details/88603387