目录:
前言
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:
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 nisnull(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; // 120std::cout << factorial_c14(5) << std::endl; // 在c++11下,会报error: body of ‘constexpr’ function ‘constexpr int factorial_c14(int)’ not a return-statementreturn 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)) = 8cout << sub_left(10, 6, 4) << endl; //((10 - 6) - 4)= 0cout << sum_right(10, 6, 4) << endl; //(10 + ( 6 + 4 )) = 20return 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;