目录:
编译期constexpr if语句/constexpr的lambda表达式:
前言
本篇博客分享,对于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 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;