c++11的一些新特性逐渐汇总

1. auto关键字

在c语言中,auto用于修饰局部变量,也称之为自动变量:

void func()
{
    auto int a;     //等价于int a
}

在c++11中,auto根据用户的初始化内容自动推导其类型:

#include <iostream>
#include <string>
#include <vector>

struct mt {
    int a;
    std::string str;
};

char mfunc()
{
    return 'v';
}

void mfunc2(std::vector<int>& vec)
{
    //auto自动推导容器模板的迭代器
    for (auto iter = vec.begin(); iter < vec.end(); ++iter) {
        std::cout << *iter << " ";
    }
    std::cout << std::endl;
}
int main(void)
{
    auto a = 11;    //auto自动推导a为int类型
    auto b = 1.34;  //auto自动推导b为float类型
    auto pa = &a;   //auto自动推导pa为int类型的指针

    std::cout << "*pa = " << *pa << std::endl;

    mt t1;
    auto t2 = t1;   //auto自动推导t2为自定义类型mt

    auto r = mfunc();   //r为char

    std::vector<int> vec;
    for (auto i = 0; i < 10; i++) {
        vec.push_back(i);
    }
    mfunc2(vec);

    return 0;
}

注意:
(1)用auto定义变量时必须使用初始化(而不是赋值),因为auto就是根据初始值内容来分配内存空间的;
(2)auto变量不能作为自定义类型的成员变量:

struct mt1 {
    auto i;
};

(3)不能定义auto数组:

auto marr[2] = {1, 2};

(4)模板实例化类型不能是auto类型:

std::vector<auto> vec1 = {1, 2, 3};
. 

(5)函数形参不可以是auto变量

void mfunc3(auto i)
{
}

2. decltype关键字

decltype可通过一个变量获取其类型,这样我们自定义的匿名类型在声明之后还可以继续用来定义变量:

#include <iostream>
#include <typeinfo>

using namespace std;

int main()
{
    struct {    /* 这是匿名结构体类型 */
        int a;
    } ms;
    decltype(ms) ms2;   /* 获取ms的类型后定义ms变量 */
    int a;

    /* 打印类型的名字,这个在不同编译器有不同的表达 */
    cout << typeid(a).name() << endl;
    cout << typeid(ms2).name() << endl;

    return 0;
}

3. 追踪函数返回值类型

用auto关键字声明函数的返回值类型,需要用”->”指定具体是什么类型:

auto mfunc(int x, int y)->int
{
    return x + y;
}

利用decltype代替代码中的int:

auto mfunc(int x, int y)->decltype(x + y)
{
    return x + y;
}

这即是追踪函数返回值类型,将其应用于泛型编程中:

template<typename T1, typename T2>
auto mfunc1(T1 t1, T2 t2)->decltype(t1 * t2)
{
    return t1 * t2;
}

因为t1、t2是泛指类型,所以程序员无法为mfunc2()指定二者相乘的类型,利用函数返回值追踪就很容易实现。

4. 直接初始化类中的成员变量

在c++11之前,对类中的成员变量的初始化要么是放在构造函数中,要么是在构造函数的初始化列表中:

class t1 {
public:
    t1(int a) : name("kyo") {
        age = a;
    }
    std::string name;
    int age;
};

c++11则支持直接初始化:

class tt {
public:
    int num = 6;
    std::string remarks {"hello"};
    t1 t = 6;
};

5. 列表初始化

在c++11之前的列表初始化运用如下:

struct st {
    int a;
    std::string str;
};
st s1 = {'a', "hello"};

上面是自定义类型的列表初始化,c++11对于列表初始化还支持于内置类型:

struct st {
    int a;
    std::string str;
};

st s1 {'a', "hello"};
int a {6};
char arr[] {1, 2, 3, 3, 5, 6};

运用于数组时显得十分方便。

6. 基于范围的for循环

int arr[] {2, 4, 6, 8};
int cnt = sizeof(arr) / sizeof(arr[0]);

在c++之前的for循环遍历数组:

for (int i = 0; i < cnt; ++i) {
    int tmp = arr[i];
    std:: cout << tmp << " ";
}
std::cout << std::endl;

在c++11等等效写法为:

for (int tmp : arr) {
    std:: cout << tmp << " ";
}
std::cout << std::endl;

运用于引用同理:

for (int i = 0; i < cnt; ++i) {
    int &tmp = arr[i];
    tmp++;
    std:: cout << tmp << " ";
}
std::cout << std::endl;

for (int &tmp : arr) {
    tmp++;
    std:: cout << tmp << " ";
}
std::cout << std::endl;

需要注意的是,上面c++11中的语法只能运用于数组大小可知的场合

7. 静态断言

assert()是c/c++用的断言函数,如下代码:

#include <assert.h>
int main(void)
{
    bool val = true;

    assert(val == false);

    printf("running...\n");

    return 0;
}

assert(val == false)代码语句表示,程序运行时若条件为真则继续往下执行,若条件为假则中断程序并提示错误:
在这里插入图片描述
c++11提供了静态断言函数,在程序编译阶段就可以中断程序。需要强调的是,因为需要在编译阶段就需要作出断言结果,断言的条件只能是常量表达式。如下用于判断系统是多少位:

int main(void)
{
    static_assert(sizeof(void* ) == 4, "local system is 64bit\n");

    printf("running...\n");

    return 0;
}

在64位的操作系统上编译:
在这里插入图片描述

8. noexcept关键字

noexcept用于声明函数不会抛出异常(君子协议):

int func2() noexcept
{
    return 0;
}

跟下面的语义是一致的:

int func2() throw()
{
    throw 1;
    return 0;
}

c++11的noexcept在编译阶段会做出优化。

9. 强类型枚举

int main(void)
{
    enum status0 {
        success,
        failed
    };

    enum status1 {
        ok,
        failed
    };
    return 0;
}

编译:
在这里插入图片描述
报错,这是因为枚举类型在同一个作用域中不能出现两次,即重复定义了。c++11中支持用class或struct关键字对enum作用域加以划分,且在使用该enum类型时需要指定作用域,称为强类型枚举:

enum class status0 {
    success,
    failed
};

enum class status1 {
    ok,
    failed
};
status0 val = status0::success; //需要指定作用域

强类型枚举还支持指定成员变量的类型:

enum class status0 {
    success,
    failed
};

enum class status1:char {
    ok,
    failed
};

status0 val = status0::success; 

printf("%d\n", sizeof(status0::failed)); //默认是4字节,跟c语言的枚举类型一样默认是int类型
printf("%d\n", sizeof(status1::ok));     //1字节

10. nullptr

int func(int a)
{
    printf("func(int )\n");
    return 0;
}

int func(int* p)
{
    printf("func(int* )\n");
    return 0;
}

int main(void)
{
    func(0);
    func(NULL);

    return 0;
}

如上重载函数,调用者希望func(0)调用的是func(int)原型函数,func(NULL)则调用func(int* ),然而:
在这里插入图片描述
编译器报错,func(NULL)不能匹配到调用函数,这是因为NULL其实质是一个0地址值。为了明确NULL指针,c++11引入了nullptr:

func(0);
func(nullptr);

在这里插入图片描述

11. constexpr常量表达式

常量表达的计算结果是发生在编译阶段,而非运行阶段。

const int get_num()
{
    return 6;
}

int main(void)
{
    enum Num {d1 = get_num(), d2};
    return 0;
}

我们知道enum成员变量必须是一个常量,然而通过get_num()函数获取得值显然是通过非常量表达式的方法获取的,即必须在运行时才能知道d1的值,所以报错:
在这里插入图片描述
关键字constexpr可用于修饰表达式为一常量表达式:

constexpr int get_num()
{
    return 6;
}

这样就编译正常了。同时还可以这么玩:

constexpr int get_num()
{
    return 6;
}

int main(void)
{

    constexpr int n = get_num();
    enum Num {d1 = n, d2};

    return 0;
}

需要强调,constexpr声明函数的限制:
(1)除了函数中只能有一个语句且该语句是return语句;
(2)函数必须有返回值且,且只能返回常量/常量表达式,不可以是全局数据。
(3)在使用前必须先定义,注意是定义,单单声明不行;

const 和 constexpr 变量之间的主要区别在于:const 变量的初始化可以延迟到运行时,而 constexpr 变量必须在编译时进行初始化。所有 constexpr 变量均为常量,因此必须使用常量表达式初始化。

12. std::array数组

std::array除了有传统数组支持随机访问、效率高、存储大小固定等特点外,还支持迭代器访问、获取容量、获得原始指针等高级功能。而且它还不会退化成指针T *给开发人员造成困惑,与内置数组类似,array对象的大小是固定的,因此,array不支持添加和删除元素以及改变窗口大小的操作

array的使用示例为:

#include <string>
#include <iterator>
#include <iostream>
#include <algorithm>
#include <array>
 
int main()
{
    // construction uses aggregate initialization
    std::array<int, 3> a1{ {1, 2, 3} }; // double-braces required in C++11 prior to the CWG 1270 revision
                                        // (not needed in C++11 after the revision and in C++14 and beyond)
    std::array<int, 3> a2 = {1, 2, 3};  // never required after =
    std::array<std::string, 2> a3 = { std::string("a"), "b" };
 
    // container operations are supported
    std::sort(a1.begin(), a1.end()); //对容器或普通数组中 [first, last) 范围内的元素进行排序,默认进行升序排序
    
	//可以将源序列复制到目的序列中,目的序列中的元素是逆序的
    std::reverse_copy(a2.begin(), a2.end(), std::ostream_iterator<int>(std::cout, " "));
 
    std::cout << '\n';
 
    // ranged for loop is supported
    for(const auto& s: a3)
        std::cout << s << ' ';
}

结果为
在这里插入图片描述

13. initializer_list

用于表示某种特定类型的值的数组,和vector一样,initializer_list也是一种模板类型

#include <initializer_list>

void func3(initializer_list<int> lists) {
  for (auto num : lists) {
    cout << num << " ";
  }
  cout << endl;
}

int main()
{
  auto al = {4,5,6};
  //func3({ 1, 2, 3 }); //1 2 3
  func3(al);//4,5,6
  
  return 0;
}

14. 智能指针

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;
}

15. 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。

16.条件变量

条件变量是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;
};

17.原子操作

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

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

18.多线程

什么是多线程这里就不过多介绍,新特性关于多线程最主要的就是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现在多数编译器还没有完全支持所有特性,先暂时了解下即可,项目中没必要着急使用。

19. std::chrono

chrono很强大,也是常用的功能,平时的打印函数耗时,休眠某段时间等,都是使用chrono。
在C++11中引入了duration、time_point和clocks,在C++20中还进一步支持了日期和时区。这里简要介绍下C++11中的这几个新特性。

duration

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

// 拿休眠一段时间举例,这里表示休眠100ms
std::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。

20. 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 << '\n'; }
   int num_;
};

void print_num(int i) { std::cout << i << '\n'; }

struct PrintNum {
   void operator()(int i) const { std::cout << i << '\n'; }
};

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) << '\n';

   // 存储到成员函数及对象的调用
   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

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

21. tuple 元组

类模板 std::tuple 是固定大小的异类值汇集,支持空列表。它是 std::pair 的推广。

std::is_trivially_destructible<Ti>::value 对 Types 中的每个 Ti 为 true ,则 tuple 的析构函数为平凡。

可以把他当做一个通用的结构体来用,不需要创建结构体又能获取结构体的特征,在某些情况下可以取代结构体使程序更简洁,直观。

基本用法

构造一个tuple

// 声明的时候使用构造函数直接初始化
std::tuple<int, double, string> item(1, 1.2, "test");
// 使用关键字 std::make_tuple
std::tuple<int, double, string> item = std::make_tuple(1, 1.2, "test");

这个tuple 类似结构体

 struct myStruct
{
    int a;
    double b;
    string c;
};

tuple的取值

// 方式一使用 get 取值
auto temp1 = get<0>(item);
auto temp2 = get<1>(item);
auto temp3 = get<2>(item);
std::cout << temp1 << std::endl;
std::cout << temp2 << std::endl;
std::cout << temp3 << std::endl;
 
// 方式二 通过std::tie解包tuple
int a;
double b;
string c;
std::tie(a, b, c) = item;
std::cout << "a=" << a << "   b=" << b << "   c=" << c << std::endl;

获取tuple长度

int nSize = std::tuple_size<decltype(item)>::value;

因为tuple的参数是变长的,也没有for_each函数,如果我们想遍历tuple中的每个元素,需要自己写代码实现。比如我要打印tuple中的每个元素。

template<class Tuple, std::size_t N>
struct TuplePrinter {
    static void print(const Tuple& t)
    {
        TuplePrinter<Tuple, N - 1>::print(t);
        std::cout << ", " << std::get<N - 1>(t);
    }
};
 
template<class Tuple>
struct TuplePrinter<Tuple, 1>{
    static void print(const Tuple& t)
    {
        std::cout << std::get<0>(t);
    }
};
 
template<class... Args>
void PrintTuple(const std::tuple<Args...>& t)
{
    std::cout << "(";
    TuplePrinter<decltype(t), sizeof...(Args)>::print(t);
    std::cout << ")\n";
}

最后附上完整的测试代码

#include <iostream>
#include <tuple>
using namespace std;
 
 
template<class Tuple, std::size_t N>
struct TuplePrinter {
    static void print(const Tuple& t)
    {
        TuplePrinter<Tuple, N - 1>::print(t);
        std::cout << ", " << std::get<N - 1>(t);
    }
};
 
template<class Tuple>
struct TuplePrinter<Tuple, 1>{
    static void print(const Tuple& t)
    {
        std::cout << std::get<0>(t);
    }
};
 
template<class... Args>
void PrintTuple(const std::tuple<Args...>& t)
{
    std::cout << "(";
    TuplePrinter<decltype(t), sizeof...(Args)>::print(t);
    std::cout << ")\n";
}
 
int main()
{
    //tuple<int, double, string> item(1, 1.2, "test");
    tuple<int, double, string> item = make_tuple(1, 1.2, "test");
    // 获取tuple元素个数(tuple长度)
    int nSize = std::tuple_size<decltype(item)>::value;
 
    PrintTuple(item);
 
    auto temp1 = get<0>(item);
    auto temp2 = get<1>(item);
    auto temp3 = get<2>(item);
    std::cout << temp1 << std::endl;
    std::cout << temp2 << std::endl;
    std::cout << temp3 << std::endl;
 
    int a;
    double b;
    string c;
    std::tie(a, b, c) = item;
    std::cout << "a=" << a << "   b=" << b << "   c=" << c << std::endl;
 
    system("pause");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值