C++新版本特性

目录:

前言

C++11的常用新特性

auto类型推导:

 auto的限制:

auto的应用: 

decltype类型推导:

decltype的实际应用: 

使用using 定义别名: 

 支持函数模板的默认模板参数 :

 tuple元组:

列表初始化:

 lambda匿名函数:

lambda的实际应用 :

for循环:

constexpr: 

右值引用:

std::move

std::forward

nullptr

 std::bind

 std::function

C++14的常用新特性

模板变量: 

 别名模板:

泛型lambda和lambda初始化捕捉

放松对constexpr函数的限制

 deprecated标记:

二进制字面量和数位分隔符: 

 折叠表达式:

类模板参数推导: 

 auto占位的非类型模板形参:

 编译期constexpr if语句/constexpr的lambda表达式:

内联变量:

结构化绑定:

if初始化:

using声明语句可以声明多个名称:


前言

本篇博客分享,对于c++11,14,17的常用c++新版本特性~


C++11的常用新特性

auto类型推导:

C++赋予auto关键字新的含义,用它做于自动类型推导

使用了auto关键字后,编译器会在编译阶段自动推导出变量的类型 

基本使用语法:

auto name = value;

name是变量名字,value是变量的初始值

注意:auto只是一个占位符,在编译期间会被真正的类型所替代,并不违背C++中的变量名必须有明确类型这一规定,只是这个是编译器自动给你推导的 

 auto的限制:

  • auto 不能在函数的参数中使用(auto 要求必须对变量进行初始化)
  • auto 不能作用于类的非静态成员变量(也就是没有 static 关键字修饰的成员变量)中
  • auto 关键字不能定义数组
  • auto 不能作用于模板参数

auto的应用: 

范围for:

int main()
{
    std::vector<int> vi{ 3,5,1,6,7,2,8 };
    for (auto e : vi) {
        std::cout << e << " ";
    }
    
    return 0;
}


//输出结果:3 5 1 6 7 2 8

以及泛型编程中~ 

decltype类型推导:

 auto 和 decltype 关键字都可以自动推导出变量的类型,但它们的用法是有区别的:

auto name = value;

decltype(exp) name = value;

其中name便是变量名,value表示赋给变量的初值~~

auto 根据 = 右边的初始值value推导出变量的类型

decltype根据圆括号内的exp表达式推出变量类型,和 = 右边的value无关

TIP:auto变量一定要初始化,decltype不用,很好理解,auto推导他需要初始化才推得动 

decltype的实际应用: 

下面就是使用的decltype推导的类型,避免使用T::iterator获得容器迭代器指针,因为有些自己实现的容器并没有iterator 

使用using 定义别名: 

C++11中的using 写法跟typedef 等价

template<typename V>
using smap = std::map<std::string, V>;

void test_using()
{
    smap<int> mp1;
    mp1["score"] = 100;
    mp1["age"] = 18;

    smap<string> mp2;
    mp2["name"] = "Merge";
    mp2["address"] = "CN";

    std::cout << mp1["age"] << ": " << mp1["score"] << endl;
    std::cout << mp2["name"] << ": " << mp2["address"] << endl;

}

// 输出:
// 18: 100
// Merge: CN

 支持函数模板的默认模板参数 :

 C++11 支持为函数模板中的参数设置默认值,在实际使用过程中,我们可以选择使用默认值,也可以尝试由编译器自行推导得到,还可以亲自指定各个模板参数的类型

template <typename R = int, typename T, typename U>
R func(T v1,U v2)
{
    return v1+v2;
}

void testFunTemplate()
{
    auto r1=func(5.5,10);                    
    auto r2=func<double>(5.5,10);            
    auto r3=func<double, int, int>(5.5,10); 
	
	cout<<"r1:"<<r1<<endl;
	cout<<"r2:"<<r2<<endl;
	cout<<"r3:"<<r3<<endl;
}

 tuple元组:

tuple的最大特点是:实例化的对象可以存储任意数量、任意类型的数据

例如要储存多个不同类型的元素时;需要函数返回多个数据的时,可以存储在tuple中后返回

void tuple_test()
{
    std::tuple<int, char> fir;

    std::tuple<int, char> mid(std::make_pair(2, '2'));      //{2,'2'}

    std::tuple<int, char> sec(1, '1');                      //右值初始化
    std::tuple<int, int, char> thd(make_tuple(6, 6, '6'));  //右值初始化

    std::tuple<int, char> fourth(sec);                      //左值初始化

    std::cout << std::get<0>(fourth) << " : " << std::get<1>(fourth) << std::endl;
    std::cout << std::get<0>(thd) << " : " << std::get<1>(thd) << " : " << std::get<2>(thd) << std::endl;
}
//输出:
//1 : 1
//6 : 6 : 6

列表初始化:

 在 C++11 中,初始化列表的适用性被大大增加了。它现在可以用于任何类型对象的初始化


class Base
{
public:
    Base(int){}
    
};
int main()
{
    Base b1(123);
    Base b2 = 123;
    Base b3 = { 123 };

    Base b4{ 123 };//c++11加入

    return 0;
}

//上述这些都是可以给定义的对象赋初值的

 lambda匿名函数:

C++11中引入了lambda~~

定义一个lambda匿名函数:

[](){}

这就是lambda的表达式三个括号,省略返回值

[外部访问方式说明符](参数){函数体};
外部变量格式                                          功能
[]        空方括号表示当前 lambda 匿名函数中不导入任何外部变量
[=]只有一个 = 等号,表示以值传递的方式导入所有外部变量
[&]只有一个 & 符号,表示以引用传递的方式导入所有外部变量
[val1,val2,...]表示以值传递的方式导入 val1、val2 等指定的外部变量,同时多个变量之间没有先后次序
[&val1,&val2,...]表示以引用传递的方式导入 val1、val2等指定的外部变量,多个变量之间没有前后次序
[val,&val2,...]以上 2 种方式还可以混合使用,变量之间没有前后次序
[=,&val1,...]表示除 val1 以引用传递的方式导入外,其它外部变量都以值传递的方式导入
[this]表示以值传递的方式导入当前的 this 指针

lambda的实际应用 :

 使用Lambda表达式在向量中查找大于10的元素并打印出来:


int main()
{
    vector<int> vi{ 1,4,21,12,13,9,30,24 };
    std::for_each(vi.begin(), vi.end(), [](int num){
        if (num > 10) std::cout << num << " ";
    });

    return 0;
}
//输出:21 12 13 30 24

 使用Lambda表达式实现自定义排序规则,并对一个字符串数组进行排序:


int main()
{
    vector<string> vs{ "Alice","Bob","Merial","Lucy","GorzenHot" };
    sort(vs.begin(), vs.end(), [](const string s1, const string s2) {
        return s1.size() < s2.size();
    });
    for (auto e : vs)cout << e << " ";
    return 0;
}
//输出:Bob Lucy Alice Merial GorzenHot

for循环:

 C++ 11 标准为 for 循环添加了一种全新的语法格式

for (declaration : expression){
    //循环体
}

示例即对于上面lambda的遍历方式~~

constexpr: 

constexpr关键字能够让指定的常量表达式在程序编译阶段计算出结果,而不需要到运行阶段
c++11中constexpr可用于修饰普通变量、函数、类的构造函数
int main()
{
    constexpr int num = 1 + 2 + 3;
    int arr[num]{ 1,2,3,4,5,6 };
    cout << arr[5];
    return 0;
}
//输出:5

右值引用:

C++11标准引入的另一种应用方式,称为右值引用。从而引出了移动语义和完美转发~~

声明和左值引用一样,右值引用也必须立即进行初始化操作,且只能使用右值进行初始化。

int num = 1;

int && a = 10;

a = 20;
cout<<a<<endl;
//输出:20

右值也必须使用右值进行初始化

std::move

C++11最重要的一个改进之一就是引入了move语义,这样在一些对象的构造时可以获取到已有的资源(如内存)而不需要通过拷贝,申请新的内存,这样移动而非拷贝将会大幅度提升性能

class Base {
public:
    Base()
        :num(new int(0)){
        cout << "构造函数" << endl;
    }
    Base(const Base& b) :num(new int(*b.num)) {
        cout << "拷贝构造函数" << endl;
    }

    Base(Base&& b) :num(b.num) {
        b.num = nullptr;
        cout << "移动构造函数" << endl;
    }
    ~Base() {
        delete num;
        cout << "析构函数" << endl;
    }
public:
    int* num;
};

int main()
{
    int* num = new int(20);
    Base a;
    Base b(a);

    Base c(std::move(b));
    return 0;
}

//构造函数
//拷贝构造函数
//移动构造函数

//析构函数
//析构函数
//析构函数

在移动构造的时候,没有新的内存申请和分配,有的只是资源转移,在大量对象的系统中,移动构造相对拷贝构造可以显著提升性能!

std::forward

完美转发:函数模板可以将自己的参数“完美”的转发给内部调用接口,不仅仅能够转移参数的值,还能转移参数的左右属性不变~~

void FTest(int& t) {
    cout << "左值" << endl;
}
void FTest(int&& t) {
    cout << "右值" << endl;
}
template <typename T>
void func(T&& t) {
    FTest(std::forward<T>(t));
}

void Test_Forward()
{
    string A = "abc";
    string&& Rval = std::move(A);

    string B(Rval);       //拷贝构造,不是移动构造
    cout << A << endl;    //此时A的资源已经被转移到 Rval了

    string C(std::forward<string>(Rval));//移动构造,保留了Rval的属性为右值,否则如果不在这 
                                         //里std::move()的话,并不能完美转发属性

    func(5);
    int x = 1;
    func(x);
}

//输出结果: abc
//          右值
//          左值

nullptr

C++11下,相比于NULL和0,使用nullptr初始化空指针,可以使我们编写的程序更健壮


void isnull(void* c) {
    cout << "void*c" << endl;
}
void isnull(int n) {
    cout << "int n" << endl;
}

void testNullptr()
{
    cout << "testNullptr:" << endl;

    isnull(0);
    //isnull(NULL); // C++ 98/03 标准中 输出 int n
    isnull(nullptr);
}

int main() {
    testNullptr();
    return 0;
}
//输出:testNullptr:
//     int n
//     void*c

 std::bind

std::bind

函数原型:

template<class Fn, class... Args>
std::bind(Fn&& fn, Args&&... args);

对其参数的解释:

  • Fn:是要绑定的可调用对象(函数,函数指针,成员函数,函数对象等)
  • Args:是对应Fn的参数列表 

std:bind函数返回一个新的可调用对象,可以同股调用该对象执行绑定的函数,并传递额外的参数.

示例:

void foo(int a, int b)
{
    cout << "a: " << a << "\t" << "b: " << b << endl;
    std::cout << "Sum : " << a + b << endl;
}
int main() {
    auto SumFunc = std::bind(foo,placeholders::_1,10);
    SumFunc(10);

    return 0;
}

//输出: a: 10   b: 10
//      Sum : 20

每个参数都可以绑定一个值或是占位符上:

  • 如果绑定一个值,那么调用返回的函数对象就会使用该值作为参数
  • 如果是一个占位符,则调用返回的函数对象会转发一个传递给调用的参数(参数顺序由占位符决定)

 std::bind的注意事项:

  • bind预先绑定的参数需要传递具体的变量或者值进去,对于预先绑定的参数是pass-by-value。除非被std::cref包装,才pass-by-reference
  • 对于事先不绑定的参数,就需要传入std::placeholders进去从 _1开始递增,这些placeholder是pass-by-reference的
  • bind的返回值是可调用的实体,可以赋值给std::function对象
  • 对于绑定的指针、引用类型参数,需要保证在调用实体之前,这些参数是可用的
  • 类的this可以通过对象或者指针来绑定

 std::function

类模板std::function是一种通用、多态的函数封装。std::function的示例可以对任何可调用的目标实体进行存储、复制、调用和操作。这些包括 普通函数、lambda表达式,bind表达式、函数指针及其他函数对象~~


C++14的常用新特性

模板变量: 

在C++14中:

  •  lambda表达式参数可以为auto类型 ,类似于函数模板
  • 可以对捕捉列表的捕获变量“赋值”
template<typename T>
constexpr T pi = T(3.1415926535897932385); //变量模板

template<typename T>
T circular(T r) {
    return pi<T> *r * r;
}
int main()
{
    cout << circular(2) << endl;
    cout << circular(2.00) << endl;
    return 0;
}

//输出: 12
//      12.5664

 别名模板:


template<typename T ,typename U>
struct A {
    T t;
    U u;
};

template<typename T>
using B = A<T, int>;


int main()
{
    B<double> b;
    b.t = 12.12;
    b.u = 34.12;
    cout << b.t << " " << b.u << endl;
}

//输出:12.12 34
//这里模板U已经被特化成int 类型,并且定义新的别名模板 B
//从此B就是一个T类型 加 一个 int 类型 的A模板

泛型lambda和lambda初始化捕捉

int main()
{
    int a = 2;
    auto fa = [a = sin(a)]() {
       cout << a << endl;
       cout << cos(a) << endl;
    };
    fa();

    cout << a << endl;

    //lambda表达式中参数可以为auto了
    auto f = [](auto a) {
        return a;
    };
    cout << f(1.1) << " " << f(298) << endl;

    return 0;
}
//输出结果:
// 0.909297
// 0.6143
// 2
// 1.1 298

放松对constexpr函数的限制

C++11中的常量表达式函数:

  • 函数体只有单一的return返回语句
  • 函数必须有返回值(不能是void函数)
  • 在使用前必须有定义
  • return返回语句表达式中不能使用非常量表达式的函数,全局数据,必须是一个常量表达式
#include <cmath>
#include <iostream>
using namespace std;

constexpr int factorial(int n) { // 在C++11或者C++14中均可以编译通过
  return n <= 1 ? 1 : (n * factorial(n - 1));
}

constexpr int factorial_c14(int n) { // 只有在C++14中可以编译通过
  int ret = 1;
  for (int i = 1; i <= n; ++i) {
    ret *= i;
  }
  return ret;
}

int main()
{
  std::cout << factorial(5) << std::endl;   // 120
  std::cout << factorial_c14(5) << std::endl; // 在c++11下,会报error: body of ‘constexpr’ 
  function ‘constexpr int factorial_c14(int)’ not a return-statement
  return 0;
}

 deprecated标记:

C++14中增加了[[deprecated]]标记,可以修饰类、函数、变量等,当程序中使用了被其修饰的模块时,编译器会产生告警,提示用户该标记标记的内容将来可能会被废弃,尽量不要使用

二进制字面量和数位分隔符: 

int main()
{
    int a = 0b0001'1111'1010;
    double b = 3.14'1592'6535;

    cout << a << " " << b << endl;
    return 0;
}
//输出:506 3.14159

 折叠表达式:

template <typename... Args>
auto sub_right(Args... args)
{
    return (args - ...);
}

template<typename... Args>
auto sub_left(Args... args)
{
    return (... - args);
}
template<typename... Args>
auto sum_right(Args... args)
{
    return (args + ...);
}

int main()
{
    cout << sub_right(10, 6, 4) << endl; //(10 - (6 -4)) = 8
    cout << sub_left(10, 6, 4) << endl;  //((10 - 6) - 4)= 0
    cout << sum_right(10, 6, 4) << endl; //(10 + ( 6 + 4 )) = 20
    return 0;
}
//输出:8 0 20

类模板参数推导: 

类模板实例化时,可以不必显示指定类型,前提是保证类型可被推导

template<typename T>
class A {
public:
    A(T,T){}
};

int main() {
    auto a = new A{ 1,2 };   //A<int> 
    A<int> *b = new A(3, 4); //A<int> 

    std::tuple t(3, 4, 5.6); //std::tuple<int,int,double> t
}

 auto占位的非类型模板形参:

template<auto T>
void foo()
{
    cout << T << endl;
}

int main()
{
    foo<100>();

    return 0;
}
//输出: 100

 编译期constexpr if语句/constexpr的lambda表达式:

lambda表达式可以再编译期进行运算,且函数体不能包含汇编语句,goto语句,try块,静态变量,线程局部存储,没有初始化的普通变量,不能动态分配内存,不能new delete等,不能为虚函数 

#include <assert.h>

template<auto flag>

constexpr void foo()
{
    //在编译期间进行判断,if和else语句不生成代码
    if constexpr (flag == true) {       //当flag为true的时候,下面的else块不生成汇编代码
        cout << "flag == true" << endl;
    }
    else  {
        cout << "flag == false" << endl;
    }
}
int main() {
    foo<true>(); //输出flag == true ,并且汇编代码中只有 cout<<"flag == true"<<endl;

    constexpr auto foo = [](int a, int b) {
        return a + b;
    };
    static_assert(foo(2, 3) == 5, "compile-time assert");
    //静态断言,用于编译期的断言

    return 0;
}

 static_assert:是静态断言,在编译期的断言,而constexpr就可以在编译期得到结果,从而提前判断.

内联变量:

扩展的inline用法,使得可以在头文件或者类内初始化静态成员变量:


// test.h
inline int val = 1;

// test.cpp
struct A
{
	inline static int val = 1;
};  

结构化绑定:

在C++11中,如果需要获取tuple元素,需要使用get<>()函数或者tie<>函数,这个函数可以把tuple中的元素转化为可以绑定到tie<>()左值的集合。

C++17中的结构化绑定,大大方便了这种操作,而且使用引用捕捉时还可以修改捕捉对象的值

#include<unordered_map>
#include<tuple>


void test1() {
    tuple person = std::make_tuple(string{ "XiaoHei" }, 26, string{ "man" });
    string name;
    int age;
    string gender;
    tie(name, age, gender) = person;
    cout << name << " " << age << " " << gender << endl;
}

struct Student {
    string name;
    int age;
};

Student getStudent() {
    return { "xiaobai",22 };
}
void test2()
{
    tuple stu = make_tuple(string("Xiaohei"), 18, string("man"));
    auto [name, age, gender] = stu;
    //这里的auto推导出
    //std::tuple<string,int,string> 类型
    cout << name << " " << age << " " << gender << endl;

    //这是按tuple 的结构化绑定
    auto [_name, _age] = getStudent();

    //这个stu就被推成一个pair 类形了
    auto stu_cls = getStudent();
    cout << _name << " " << _age << endl;

    unordered_map<string, int> mstu;
    mstu.emplace("张三", 33);
    mstu.emplace("李四", 44);
    for (auto [name, age] : mstu) {
        cout << name << " " << age << endl;
    }

}   

int main()
{
    test1();
    test2();
    return 0;
}

使用结构化绑定,可以将一个复杂对象(元组,map,make_pair,包含多个成员的结构体 等)分解其各个成员,并将这些成员绑定到对应的变量当中,无需显示的通过函数或者成员访问来获取每个成员的值.

if初始化:

#include <iostream>
#include <unordered_map>
#include <tuple>

void c11_fun()
{

	std::unordered_map<std::string, int> students{{"liBai", 18}, {"hanXin", 19}};
	auto iter = students.find("hanXin");
	if (iter != students.end())
	{
		std::cout << iter->second << std::endl;
	}
}

void c17_fun()
{
	std::unordered_map<std::string, int> students{{"liBai", 18}, {"hanXin", 19}};
	if (auto iter = students.find("hanXin"); iter != students.end())
	{
		std::cout << iter->second << std::endl;
	}
}

int main()
{
	//c11_fun();
	c17_fun();

	return 0;
}

using声明语句可以声明多个名称:

using std::cout , std::endl;

 

 

  • 14
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Obto-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值