文章内部代码地址:https://gitee.com/zhr2000/c-new-features
auto & decltype
这两个关键字在编译期就推到出变量或者表达式的类型
- auto:可以通过=右边的类型推导出变量的类型(自动推导)
- decltype:用于推导表达式类型,只用于编译器分析表达式的类型,表达式实际不会进行运算。主要用在模板和泛型编程中(反推导)。
- auto和decltype的区别:auto是通过初始化表达式推导出类型,而decltype是通过普通表达式的返回值推导出类型。
- RTTI机制:为每个类型产生一个type_info类型数据,程序员可以在程序中使用typeid随时查询一个变量的类型,typeid就会返回变量相应的type_info数据,type_info的name成员可以返回类型的名字。在C++11中,增加了hash_code这个成员函数,返回该类型唯一的哈希值,以供程序员对变量类型随时进行比较。(完整的解释),RTTI只能识别出类型但是不能使用。
- 追踪返回值类型:函数返回值无法确定的时候
//追踪返回值类型(主要是用在泛型编程)
template<typename t1,typename t2>
auto function(const t1 x,const t2 y) ->decltype(x+y){
return x+y;
}
初始化
- 类内成员初始化:
// 参数列表初始化
class people{
public:
people(int x,string y) : age(x),name(y){ };
int age;
string name;
};
//类中成员变量可以直接初始化
class people2{
public:
//直接用 =
int age = 10;
//用 { }
string name {"hello"};
people son {5,"world"};
};
- 列表初始化可以防止类型收窄 (但是有些编译器是可以编过但是会有警告)
// 列表初始化防止类型收窄
int a = 1024;
char b = a; //会造成数据丢失
cout << b << endl; //不会报错
int c = 1024;
char d {c}; //会报警告
cout << d <<endl;
基于范围的for循环
- 基于范围的for循环 传递的是值,不是传递的地址
//基于范围的for循环传递的是值,不是传递的地址
int a[] {1,2,3,4,5,6,7};
for(auto temp : a){
cout << temp <<endl;
}
for(auto& temp :a){
temp = temp*2;
cout << temp <<endl;
}
静态断言
- static_assert :与传统断言的区别是程序还没运行的时候就能检查条件是否满足,因为是静态的。
下面代码是传统断言(运行时才能检查)和静态断言的区别。
//传统的断言(c语言的头文件):
//运行时检查条件,如果条件为真,往下执行,如果条件为假,中断并提示错误
auto flag = true;
assert(flag == true);
cout << "hello assert" << endl;
//c++11的静态断言,
//static_assert (常量表达式, “提示的字符串”)
static_assert(sizeof(void*)==8,"64位系统不支持");
noexcept 修饰符
noexcept 除了声明函数不抛出异常,还是一个运算符,它能够用来说明函数是否会抛出异常,
例如在我们希望只有在T是一个基础类型时复制函数才会被声明为noexcept,因为基础类型的复制是不会发生异常的。这时就需要用到带参数的noexcept了:
template <class T>
T copy(const T &o) noexcept(std::is_fundamental<T>::value) {
// ...
}
这段代码通过std::is_fundamental 来判断T是否为基础类型,如果T是基础类型,则复制函数被声明noexcept(true)
即不会抛出异常。反之,函数被声明为noexcept(false),表示函数有可能抛出异常。由于noexcept对表达式的评估是在编译阶段执行的,因此表达式必须是一个常量表达式。
void function()throw(){ }
void function2()noexcept; //不抛出异常
void function3()noexcept(1==1){ }; //表达式为真抛出异常,表达式为假不抛出异常
nullptr
- 解决了之前NULL 和 0相同的问题(二义性),nullptr只能赋值给指针变量。
强类型枚举
- c++11之前是不能定义两个相同的枚举的
//以前的枚举是不能定义两个相同的
enum status{ok,err};
enum status2{ok,err}; //会报错
- C++11 强类型枚举:enum后面加 class 或者 struct 关键字,在使用的时候要加作用域,强类型枚举还可以指定成员的类型(必须为基础数据类型)
//强类型枚举,enum后面加 class 或者 struct 关键字,在使用的时候要加作用域
enum class S {ok,err};
enum struct S2 {ok ,err};
S flag = S :: ok ;
S2 flag2 = S2 :: ok;
enum class S3 : char {ok.err};
常量表达式 constexpr
- 允许一些计算发生在编译时,只能有 函数体中只能有一个 return 语句,但是允许包含typedef,using ,静态断言。
- 类中使用constexpr进行构造
class date{
public:
//里面必须式基础数据类型
constexpr date(int x,int y) : age(x),male(y){
}
constexpr int getage(){
return this->age;
}
constexpr int getmale(){
return this->male;
}
private:
int age;
int male;
};
自定义字面量
- 定义字面量(自定义后缀),名字要求 operate""xxx()
- 只有这些签名是合法的,后面四个中,第二个参数会自动推导字符串的长度,注意第一个不是字符串,而是叫做原始字面量,就是说传入的是啥就是啥
char const*
unsigned long long
long double
char const*, std::size_t
wchar_t const*, std::size_t
char16_t const*, std::size_t
char32_t const*, std::size_t
//注意事项
char const* operator"" _r(char const* s)
{
return s;
}
int main()
{
std::cout << 12_r << '\n';
//输出值是12
}
字面量的返回值并没有被严格限定。我们完全可以提供相容类型的返回值
具体使用方法
原生字符串字面值
- cout << R"(hello\n world)" <<endl;
继承构造
- 允许派生类继承基类的构造函数(默认,复制,移动构造函数除外)
- 只能初始化基类的成员变量
public:
//原始写法
B(int x,int y):A(x,y){
}
//c++11新写法 继承构造
using A::A ;
委托构造函数
//委托构造函数
A():A(10,"mike"){
cout << "我是无参构造函数" <<endl;
};
private:
A(int a,string b):age(a),name(b){
cout << "我是两个参数的构造函数"<<endl;
};
继承控制 final 和 override
- final:阻止类 的进一步派生和 虚函数 的进一步重写
- override:确保在派生类中声明的函数跟基类有相同的函数签名
类默认函数的控制 default 和 delete 函数
- delete 不仅可以修饰系统默认的函数,自己定义的类函数也可以修饰,作用是禁用某个函数
- 当用户自定义了构造函数后,编译器就不会再提供构造函数,default就是让系统继续提供默认的类函数,效率比自己写的函数高
函数模板的默认模板参数
//c++11 支持普通的函数模板带默认参数
template<typename T = int>
T TESTfunction(T a){return a};
//c++98 和 c++11都支持类模板中带默认的参数
//但是参数必须从右往左
template <typename T ,typename T2 = int>
class TEST{
};
- 可变参数的模板函数
//可变参数的模板函数
template<typename ... T> // T叫模板参数包
// args叫函数参数包
void function(T ... args){
//获取可变参数的个数
cout << sizeof ... (args) <<endl;
}
- 获取参数包的展开 非常重要
//可变参数模板的 参数包的展开
//递归的终止函数
void function2(){
cout << "null" << endl;
}
template<typename T1,typename ... T2>
void function2(T1 first ,T2 ... args){
cout << first <<endl;
//递归调用function2
function2(args ...);
}
右值引用
- 不能把一个左值赋值给右值引用
//左值引用
void test(int &tmp){
cout << "左值 = " << tmp <<endl;
}
//右值引用
void test(int && tmp){
cout << "右值 = " << tmp <<endl;
}
int main(){
int a = 10; //a 是一个左值
test(a);
test(256); //256 是一个常数,是右值
return 0;
}
返回值优化技术(移动语义)
- 针对临时对象的维护会极大影响程序的运行速度(对象的创建和销毁)
- 移动构造函数,但是现在的编译器一般都会自动进行优化。使用的主要还是右值引用。我们可以定义一个参数为右值的构造函数,在里面接管右值的资源。把右值引用作为函数参数,这就少了一次临时变量的构造的过程。
智能指针
- unique_ptr:
- shared_ptr:多个此对象可以同时绑定同一个堆区,可以用计数器来查看此堆区绑定了多少个shared_ptr。
- weak_ptr: 主要是配合 share_ptr 来使用,它是没有重载 * 和 -> 运算符的,此对象不与堆区内存绑定,可以通过lock()函数来获取share_ptr对象,若use_count为空,则返回nullptr
#include<iostream>
//智能指针的头文件
#include <memory>
using namespace std;
int main(){
/*
unique_ptr
*/
unique_ptr<string> ptr1 (new string("zhr")); //创建一个int型的指针,指向堆区新开辟的这个空间
cout << "*ptr1 = " << *ptr1 <<endl; //本质是ptr1这个对象里面有个指针指向这个堆区空间,然后重载 * 运算符。自动释放
//unique_ptr<string> ptr2 = ptr1; //err 禁止使用拷贝构造
unique_ptr<string> ptr2 = std :: move(ptr1); //可以使用移动构造
cout << "*ptr2 = " << *ptr2 <<endl;
//使用reset更改堆区的值
ptr2.reset(new string("nihao"));
auto * p = ptr2.release(); //释放控制权,但是不释放堆内存
cout <<"*p = " << *p << endl;
/*
shared_ptr
**/
shared_ptr<int> ptr3 (new int(22));
shared_ptr<int> ptr4 = ptr3;
//查看 use_count 计数器
cout << "use_count: "<< ptr3.use_count() << endl;
/*
weak_ptr ,主要是配合 share_ptr 来使用,它是没有重载 * 和 -> 运算符的,
此对象不与堆区内存绑定,可以通过lock()函数来获取share_ptr对象,若use_count为空,则返回nullptr
**/
weak_ptr<int> wp = ptr3;
cout << "use_count" << ptr3.use_count() <<endl;
//虽然wp和堆区空间不绑定,但是可以通过lock函数获取 shared_ptr<int> 对象
decltype(ptr3) ptr5 = wp.lock();
cout << "use_count:" <<ptr5.use_count() <<endl;
return 0;
}
bind
- 注:function<void(void)> f1 和lambda,传统C函数,类中的成员函数都叫可调用类型。提高了程序设计的灵活性c++11 function<>的使用以及什么情况下使用
#include <iostream>
//头文件
#include <functional>
using namespace std;
//bind(函数,占位符);
void func(int x, int y)
{
cout << x << "," << y << endl;
}
class test
{
public:
void func(int x, int y)
{
cout << x << "," << y << endl;
}
int a;
};
main()
{
bind(func, 21, 22)();
//占位符 std::placeholders::_1 ,std::placeholders::_2
bind(func, std::placeholders::_1, std::placeholders::_2)(21, 22);
bind(func, 21, std::placeholders::_1)(22);
/*******************************************************/
cout << "**************" << endl;
//绑定成员函数
test obj;
function<void(int,int)> f = bind(test::func,obj,placeholders::_1,placeholders::_2);
f(21,22);
//绑定成员变量
function<int&()> f2 = bind(&test::a,&obj);
f2() = 22;
cout << "obj.a = " << obj.a << endl;
return 0;
}
lambda
- 实际上,仿函数是编译器实现lambda的一种方式,用过编译器都是把lambda表达式转化为一个仿函数对象。
- lambda表达式在 c++11中被称为 “闭包类型”,每一个lambda表达式会产生一个临时对象(右值),lambda函数并非函数指针,不过允许将lambda表达式向函数指针转换,但前提是没有捕获任何变量。
- 利用typedef定义函数指针
lambda 表达式的优势 : 匿名函数
#include <iostream>
#include <functional>
#include <algorithm> // foreach
#include <vector>
using namespace std;
/* 1,捕获列表 []
2,参数 ()
3,函数体{}
4,如果有返回值的话在()后加 " -> 返回值类型 "
**/
//实际上,仿函数是编译器实现lambda的一种方式,用过编译器都是把lambda表达式转化为一个仿函数对象。
// lambda表达式在 c++11中被称为 “闭包类型”,每一个lambda表达式会产生一个临时对象(右值),lambda函数并非函数指针,
//不过允许将lambda表达式向函数指针转换,但前提是没有捕获任何变量。
int tmp = 2;
class test{
public:
int i {0};
void func(){
int inValue = 3;
//[this] 可以捕获类成员变量,全局变量,不能捕获局部变量
auto f8 = [this]{
cout << i << "," << tmp <<endl;
//cout << inValue << endl;
};
f8();
}
};
int main()
{
int age = 10;
string name = "zhr";
auto f = [age, name]()
{
cout << age << "," << name << endl;
};
f();
auto f1 = [=]()
{
cout << age << "," << name << endl;
};
f1();
auto f2 = [age, name](int x, int y)
{
cout << x << "," << y << endl;
};
f2(21, 22);
//以值传递的方式传给lambda表达式 [=]
auto f3 = [=]()
{
cout << age << "," << name << endl;
};
f3();
//以引用的方式捕获 [&]
auto f4 = [&]()
{
cout << age << "," << name << endl;
};
f4();
//一部分以值传递,一部分以引用传递,下面的是一种错误的写法,&或者= 要放在前面
auto f5 = [&,age]{
cout << age << "," << name << endl;
};
f5();
// auto f5 = [age,&]{
// cout << age << "," << name << endl;
// };
//默认情况下,lambda函数传入的参数都是以const修饰的,
//值传递无法修改,想要修改加 mutable或者改为 引用传递
auto f6 = [&]{
age++;
cout << age << "," << name << endl;
};
f6();
//注意:此时不能省略()即使没有参数传入!!!
auto f7 = [=]() mutable{
age ++;
cout << age << "," << name << endl;
};
f7();
//验证 [this]
test obj;
obj.func();
//将lambda表达式绑定到仿函数
function<void(void)> f9 = [](){cout << "i am zhr!"<<endl;};
f9();
function<void(int)> f10 = [=](int x){cout << "i am x = "<< x << endl;};
f10(22);
//通过 bind 来绑定。 bind(函数,变量占位符)
function<void(int)> f11 = bind([](int x){cout << "in bind,i am x = "<< x << endl;} , std::placeholders::_1);
f11(22);
auto f12 = [](int a,int b){return a+b;};
//定义一个函数指针类型
typedef int (*PFUNC)(int , int);
PFUNC p1 = f12; //lambda表达式转换为指针,此时不允许捕获外部变量 []
cout << p1(21,22) << endl;
/*
lambda 表达式的优势 : 匿名函数
**/
vector<int> nums1;
decltype(nums1) nums2;
for(int i = 0;i < 10; i++){
nums1.emplace_back(i);
}
int tmp = 5;
for_each(nums1.begin(),nums1.end(),[&,tmp](int n){
if(n > tmp){
nums2.emplace_back(n);
}
});
cout <<endl;
for_each(nums2.begin(),nums2.end(),[](int n){
cout << n <<",";
});
return 0;
}