auto、decltype自动类型推导
auto并不代表实际的数据类型,且auto并不是在任何场合下都能推导出变量的实际类型,使用auto必须初始化。
auto 变量名 = 变量值;
当变量不是指针或引用类型时,推导的结果中不会保留const、volatile关键字;
当变量是指针或引用类型时,推导的结果中会保留const、volatile关键字。
不允许使用auto四个场景:
1.不能作为函数参数使用
int func(auto a,auto b)//error
{cout << a << "" << endl;
}
2. 不能用于类的非静态成员初始化
class Test
{auto a1 = 0;//errorstatic auto a2 = 0;//error 类的静态非常量成员不允许在类内直接初始化static int a;static const auto a3 = 0;//ok
};
auto Test::a = 1;//error
3.不能使用auto关键字定义数组
int func()
{int array[] = { 1,2,3,4,5 };auto t1 = array;//ok t1被推导为int*类型auto t2[] = array;//error auto无法定义数组auto t3[] = { 1,2,3,4,5 };//error auto无法定义数组
}
4.无法用auto推出模板参数
template<typename T>
struct Test {T a};
int func()
{Test<double>t;Test<auto>t1 = t;//error 无法推出模板类型
}
相比于auto用于推导变量类型,decltype则用于推导表达式类型,这里只用于编译器分析表达式的类型,表达式实际不会进行运算。
decltype(表达式)
const int& i = 1;decltype(i) b = 1;//b是const int&int a = 2;decltype(a + 3.14) c = 5.15;//c是double
增强for
for(declaration:expression)
{
//循环体
}
declaration表示遍历声明,在遍历过程中,会将遍历到的元素拷贝到声明的变量中,expression是要遍历的对象,它可以是表达式、容器、数组、初始化列表等。
lambda表达式
概念
sort(vec.begin(),vec.end,[ ](int a,int b){return a>b;}); //作为匿名函数使用
表示形式
具体语法
解释:
举例:
能不能改变:int a=100;
- [ = ](){return a = 10; } //错误 常函数,a是值传递,不可以改变
- [ & ](){return a = 10;} //可以 a是引用传递,a会被改写成10
- [ = ]()mutable{return a = 10;} //可以 mutable 可变,副本a会被改写成10,外面a不变
- [ & ]()mutable{return a = 10:} //可以 a是引用传递,mutable 可变,a会被改写成10
#include<iostream>
using namespace std;class AA
{
public:AA(){auto f0 = [this](){return this;};cout<<f0()<<endl;}
};int val1 = 10;
int main()
{auto f1 = [](){return val1 = 100;}; //全局的变量,不用捕获,可以直接使用cout<<f1()<<endl; //100cout<<val1<<endl; //100int j = 5;auto f2 = [&j](){return j = 10;};auto f3 = [j](){return j;}; //先后执行会有不同cout<<f2()<<endl; //10cout<<j<<endl; //10//修改的是副本auto f4 = [j]()mutable{return j = 100;};auto f5 = [j]()mutable{return j = 200;};cout<<f4()<<endl; //100cout<<j<<endl; //10cout<<f5()<<endl; //200cout<<j<<endl; //10auto f6 = [&j]()mutable{return j = 300;};cout<<f6()<<endl; //300cout<<j<<endl; //300AA a;return 0;
}
智能指针
头文件:#include<memory>
auto_ptr(已在C++11中弃用)
创建方式:
- 有参构造
- reset()方法
- 赋值
存在的问题:指针的所有权交出的太随意,交出后,原指针为空,后续通过该指针调用会出问题。
unique_ptr
唯一所有权智能指针,在auto_ptr的基础上禁止拷贝构造与赋值,通过move()对使用权进行转移,避免auto_ptr在用户无意识情况下将指针的所有权交出。
使用场景:单例模式
shared_ptr
#include <iostream>
#include<vector>
#include<string>
#include<memory>
using namespace std;class AAAA{
private:string s;
public:AAAA( const string str){s = str;cout<<"AAAA:"<< s << endl;}~AAAA(){cout<<"~AAAA" << endl;}void say(){cout<<"say:"<< s << endl;}
};int main() {shared_ptr<AAAA> sp1( new AAAA("shared_ptr1"));shared_ptr<AAAA> sp2;sp2.reset( new AAAA("shared_ptr2"));shared_ptr<AAAA> sp3;sp3 = make_shared <AAAA>("share_ptr3");{shared_ptr<AAAA>sp4;sp4=sp3; //指向同一个 共享cout<< sp4.use_count()<< endl;//2}sp3.get()->say();//say:share_ptr3sp3->say();//say:share_ptr3cout<<sp3.use_count()<<endl;//查看引用计数 1sp3.reset();//主动回收空间return 0;
}
存在的问题:
1、循环引用
什么是循环引用?举例:
解释:此时spc.use_count()为2,spb.use_count()也为2,走到{ }的结尾处,对pc,pb回收,此时引用计数都变为1,空间无法回收,这就是循环引用。
2、多线程下的线程安全问题
为了保证空间正确回收,引用计数+1或-1是线程安全的,但关于指针指向空间的使用,是非线程安全,所以在多线程下使用这个空间要加锁。
weak_ptr
同时在weak_ptr销毁时,对应空间的引用计数也不会减1。
weak_ptr,弱引用计数智能指针,它的提出主要是解决shared_ptr中循环引用的问题。
怎么解决?举例:
解释:将spb或spc由shared_ptr改成weak_ptr,这里我们改spb。此时spc.use_count()为1,spb.use_count()为2,回收pc、pb后,spc.use_count()为0,回收其空间,当该空间回收后,该空间的spc对象也就不存在了,这时spb.use_count()也由1变为0,对应的空间也得到了回收。
- 可以使用 lock()方法获取与weak_ptr捆绑的shared_ptr。lock() 方法会尝试升级 weak_ptr 到一个 shared_ptr。如果 weak_ptr 所指向的对象仍然存在(即没有被销毁),lock() 方法会成功并返回一个 shared_ptr;如果对象已经被销毁,它会返回一个空的 shared_ptr。
- weak_ptr不能直接调用函数,如果想调用,需要先获得与weak_ptr捆绑的shared_ptr,再通过shared_ptr进行调用。
- expire()方法用于检查 weak_ptr 是否指向一个有效的 shared_ptr。如果 weak_ptr 所指向的对象已经被销毁(即所有与之关联的 shared_ptr 都已被释放),expire() 将返回 true,表示 weak_ptr 失效。
举例:
#include <iostream>
#include <memory>class MyClass {
public:void doSomething() {std::cout << "Doing something" << std::endl;}
};int main() {std::shared_ptr<MyClass> sp(new MyClass());std::weak_ptr<MyClass> wp(sp); // 从 shared_ptr 创建 weak_ptr//std::weak_ptr<MyClass> wp = sp;// 模拟 shared_ptr 被销毁的情况sp.reset();if(wp.expired()){std::cout<<"weak_ptr失效"<<std::endl;//weak_ptr失效}// 尝试通过 weak_ptr 获取 shared_ptrstd::shared_ptr<MyClass> lockedSp = wp.lock();if (lockedSp) {lockedSp->doSomething(); // 如果 shared_ptr 不为空,调用成员函数} else {std::cout << "Object has been destroyed." << std::endl;//Object has been destroyed.}return 0;
}
总结:
右值引用
概念
引用分为左值引用,右值引用,常引用。
返回值优化
- 当函数需要返回一个对象实例的时候,就会创建一个临时对象并通过拷贝构造函数将目标对象复制到临时对象,这里有拷贝构造函数和析构函数会被多余的调用到,有代价。而通过返回值优化,C++标准允许省略调用这些拷贝构造函数。
举例:
左值引用与右值引用举例:
int main()
{//以下的都是左值int* p = new int(0);int a = 1;const int b = 2;//以下是对上面左值的左值引用int*& rp = p;int& ra = a;const int& rb = b;int& pvalue = *p;return 0;
}
int main()
{double x = 1.1, y = 2.2;//以下是常见的右值10;x + y;fmin(x, y);//以下是对右值的右值引用int&& rr1 = 10;double&& rr2 = x + y;double&& rr3 = fmin(x, y);//这里编译会报错:“error C2106: “=”: 左操作数必须为左值”10 = 1;x + y = 1;fmin(x, y) = 1;return 0;
}
表示形式
举例:
总结:左值引用不可引用右值,右值引用也不可引用左值,但可以引用移动的左值。
作用
- 引用临时对象,延长临时对象生命周期
- 实现移动语义和完美转发,消除两个对象交互时不必要的对象拷贝
- 能够更简洁明确地定义泛型函数。(更准确的传参和调用函数)
解释:
转移语义可以将资源(堆,系统对象等)从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,能够大幅度提高C++应用程序的性能
举例:
#include<iostream>
using namespace std;#include<vector>vector<int> getVec(vector<int>& vec)
{cout<<"fun::vec addr:"<<&vec.front()<<endl; //fun::vec addr:0x1e1275d0return vec;
}vector<int>&& getVec2(vector<int>& vec)
{cout<<"fun::vec2 addr:"<<&vec.front()<<endl; //fun::vec2 addr:0x1e1275d0return move(vec);
}int main() {//转移语义vector<int> vec = {1,2,3,4,5,6};cout<<"vec addr:"<<&vec.front()<<endl; //vec addr:0x1e1275d0vector<int> vec1 = getVec(vec);cout<<"vec1 addr:"<<&vec1.front()<<endl; //vec1 addr:0x1e1285f8vector<int> vec2 = getVec2(vec);cout<<"vec2 addr:"<<&vec2.front()<<endl; //vec2 addr:0x1e1275d0cout<<"vec items:"<<endl;for(auto& num : vec){cout<<num<<" "; //空}cout<<endl;for(auto& num : vec2){cout<<num<<" "; //1 2 3 4 5 6 }cout<<endl;return 0;
}
右值引用可以实现精确传递
- 精确传递适用场景:需要将一组参数原封不动的传递给另一个函数。
- 精确传递就是在参数传递过程中,所有属性和参数值都不能改变。
- 在泛型函数中,这样的需求非常普遍。
举例:
#include<iostream>
using namespace std;template <typename T>
void forward_value(const T& val)
{cout<<"forward_value(const T& val)"<<endl;
}template <typename T>
void forward_value(T& val)
{cout<<"forward_value(T& val)"<<endl;
}int main() {//精准传参int a = 0;const int &b = 1;forward_value(a); //int&forward_value(b); //const int&forward_value(2); //const int&return 0;
}
由上述代码可知,常引用以及常量匹配的都是const int&,那么我们再加上右值引用,会发生什么呢?
#include<iostream>
using namespace std;template <typename T>
void forward_value(const T& val)
{cout<<"forward_value(const T& val)"<<endl;
}template <typename T>
void forward_value(T& val)
{cout<<"forward_value(T& val)"<<endl;
}template <typename T>
void forward_value(T&& val)
{cout<<"forward_value(T&& val)"<<endl;
}int main() {//精准传参int a = 0;const int &b = 1;forward_value(a); //int&forward_value(b); //const int&forward_value(2); //int&&return 0;
}
加上右值引用后,结果发生了变化,可见右值引用可以实现精准传参。
问题:
四种类型转换
C语言里面有强制类型转换,为什么不继续沿用,而存在这四种类型转换?
- C++在设计时,强调类型安全,但是C中的类型转换可谓无所不能,沿用的话,会破坏C++的类型系统,还会留下很多安全隐患。
- 此外,四种类型转换还有他们使用的不同场合和功能,组合使用较C中的强制类型转换更强大。
static_cast 静态转换
编译时确定的
使用场景:
- 相关内容的转换 例:数与数之间、int short char 之间、int*与 void*之间
- 继承中对象的向上和向下转换(向下转换,没有检查,可能会有问题)
举例:
父类A 子类 A1(fun1) A2(fun2) A3(fun3)
A* pa= new A1;
//向下转换
A2 *pa2 = static_cast<A2*>(pa); // 转换可以,但是后面使用就出问题了
pa2 -> fun2();注:
- A是B的父类,B->A 继承中对象的向上转换,A->B 向下转换
- 不相关的不能转换:student*与int*之间不能转换,char*与int*之间也不能(因为不安全),int -> int* 不可以,int*->int 也不可以
相当于限制版的强制类型转换
代码举例:
#include<iostream>
using namespace std;class student{};
class A1
{
public:virtual void fun(){}
};
class A2:public A1{};
class A3:public A1{};
class A4:public A2{};int main() {int a = 0;int *p = &a;char c = 0;char* pc = &c;student st;{//---------------static_cast-----------------//相关内容转换//p = static_cast<int*>(pc);//char*->int* 不允许void* lpvoid = static_cast<void*>(pc);a = static_cast<int>(c);//student* ps = static_cast<student*>(p);//int*->student* 不允许//p = static_cast<int*>(&st);//student*->int* 不允许//a = static_cast<int>(p);//int*->int 不允许//p = static_cast<int*>(a);//int->int* 不允许A2* pa2 = (A2*)(new A1);//子类指针默认无法指向父类对象,除非强转//继承关系中对象的向上转换A1* pa1 = static_cast<A1*>(pa2);pa1 = new A2;//继承关系中对象的向下转换pa2 = static_cast<A2*>(pa1);A3* pa3 = static_cast<A3*>(pa1);if(pa3){cout<<"pa3 is valid"<<endl;//pa3会取到值,但后续如果执行A3里面的函数会有问题}//C语言强制类型转换student* ps2 = (student*)(p);p = (int*)&st;}return 0;
}
dynamic_cast 动态转换
- 只适用于继承上的两个类之间转换
- 继承的向上转换效果等同于static_cast
- 继承的向下转换会进行检验
- 向下转换安全(即父类指针要转换为子类指针,恰巧这个父类指针指向这个子类对象,可以得到对应结果)
- 向下转换不安全(即父类指针要转换为子类指针,恰巧这个父类指针不指向这个子类对象,返回结果为空)
- 向下转换,父类中必须包含虚函数(static_cast 没有这个要求)
- 特殊情况
- 向下转换安全(即父类指针要转换为子类指针,恰巧这个父类指针指向这个子类的子类),通俗来说就是爷爷指向孙子,此时爷爷想转化为父亲。
代码举例:
A1、A2、A3、A4之间关系图:
#include<iostream>
using namespace std;class student{};
class A1
{
public:virtual void fun(){}
};
class A2:public A1{};
class A3:public A1{};
class A4:public A2{};int main() {int a = 0;int *p = &a;char c = 0;char* pc = &c;student st;{A2* pa2 = (A2*)(new A1);//继承关系中对象的向上转换A1* pa1 = dynamic_cast<A1*>(pa2);pa1 = new A2;//继承关系中对象的向下转换pa2 = dynamic_cast<A2*>(pa1);A3* pa3 = dynamic_cast<A3*>(pa1);if(pa3){cout<<"pa3 is valid"<<endl;}else{cout<<"pa3 is invalid"<<endl;//pa3 is invalid}//特殊情况,向下转换安全:父类指针要转换为子类指针,恰巧这个父类指针指向这个子类的子类pa1 = new A4;pa2 = dynamic_cast<A2*>(pa1);if(pa2){cout<<"pa2 is valid"<<endl;//pa2 is valid}else{cout<<"pa2 is invalid"<<endl;}pa1 = new A2;A4* pa4 = dynamic_cast<A4*>(pa1);if(pa4){cout<<"pa4 is valid"<<endl;}else{cout<<"pa4 is invalid"<<endl;//pa4 is invalid}//交叉转换//pa3 = static_cast<A3*>(pa2); //相当于A1转A2,A2又转A3,静态转换不可以pa3 = dynamic_cast<A3*>(pa2); //动态转换可以}return 0;
}
为什么要用dynamic_cast?
对于动态转换中继承的向下转换,怎么进行安全检验的?
根据虚函数的入口地址进行检验,如果虚函数的入口地址相同,那么是安全的。
static_cast与dynamic_cast区别?
- static_cast 支持相关数据类型转化,dynamic_cast不支持
- 对于继承关系中的转换,static_cast可以向上和向下转换,dynamic_cast向上转换与static_cast一致,向下转换,父类要有虚函数,并且存在检验机制
- static_cast不支持交叉转换,只能是向上下,dynamic_cast可以交叉转换
const_cast 去常转换
int* p=const_cast<int*>( const_int_lp);
const->非const
volatile->非volatile例: const A*-> A* 或者 const A ->A
作用:
- 由于常对象只能调用常函数,并且对象的属性不能修改,去常转换后,可以调用任何类成员函数,并且可以修改属性值
volatile
- 防止修饰变量编译优化
- 避免修饰变量缓存和内存不一致(每次到其地址读取,先写后读)
reinterpret_cast 重解释类型转换
- 是C++里的强制类型转换符
- 相当于(type),无所不能
- 有安全隐患,一般编译实在不过的时候使用
委托构造函数&继承构造函数
什么是委托构造函数?
允许在构造函数中调用同一个类中另外一个构造函数 ,实现代码的简化,但要注意不可出现闭环,下面的例子是链式调用,这是可以的,如果3调用1的同时,1也调用3,这是不可以的。同时构造函数的调用应写在初始化列表中,不应写在函数体内部,否则可能会提示形参重定义。
class Test
{
private:int m_max;int m_min;int m_mid;
public:Test() {};Test(int max){this->m_max = max > 0 ? max : 100;}Test(int max, int min):Test(max)//调用Test(max){this->m_min = min > 0 && min < max ? min : 1;}Test(int max, int min, int mid):Test(max, min)//调用Test(max, min){this->m_mid = mid<max && mid>min ? min : 50;}
};
什么是继承构造函数?
允许派生类继承基类的构造函数,从而减少代码的重复。在没有继承构造函数之前,派生类需要显示地定义构造函数,并在构造函数中调用基类的构造函数来初始化基类的部分,有了继承构造函数以后,就可以让派生类自动继承并使用基类的构造函数,这样就避免了显示地调用。
class Base {
public:Base(int i) :m_i(i) {}Base(int i, double j) :m_i(i),m_j(j) {}Base(int i, double j, string k) :m_i(i), m_j(j), m_k(k) {}int m_i;double m_j;string m_k;
};
class Child :public Base
{
public:Child(int i) :Base(i) {}Child(int i, double j) :Base(i, j) {}Child(int i, double j, string k) :Base(i, j, k) {}
};int main() {Child c(1, 1.2, "Bob");cout << c.m_i << " " << c.m_j << " " << c.m_k << endl;//1 1.2 Bobreturn 0;
}
引入后:
class Base {
public:Base(int i) :m_i(i) {}Base(int i, double j) :m_i(i),m_j(j) {}Base(int i, double j, string k) :m_i(i), m_j(j), m_k(k) {}int m_i;double m_j;string m_k;
};
class Child :public Base
{
public:using Base::Base;
};int main() {Child c(1, 1.2, "Bob");cout << c.m_i << " " << c.m_j << " " << c.m_k << endl;//1 1.2 Bobreturn 0;
}
constexpr
用于修饰函数和变量,在编译期进行运算,从而提高程序的性能。适用于常量表达式,要求值或函数能够在编译时确定。
#include <iostream>// constexpr 函数
constexpr int factorial(int n) {return n <= 1 ? 1 : n * factorial(n - 1);
}int main() {constexpr int fact5 = factorial(5); // 编译期求值std::cout << "Factorial of 5 is: " << fact5 << std::endl;// 运行时求值int n = 5;int fact = factorial(n); // 在运行时计算std::cout << "Factorial of 5 is: " << fact << std::endl;return 0;
}
std::function与std::bind
std::function介绍
std::function是一个函数封装器,它可以存储、调用和传递任何可调用对象。
满足以下条件之一就可称为可调用对象:
- 函数指针
- 类成员(函数)指针
- lambda表达式
- bind表达式或其它函数对象
- 具有operator()成员函数的类对象(传说中的仿函数)
- 可被转换为函数指针的类对象
std::bind介绍
std::bind可以将可调用对象和参数一起绑定,绑定后的结果使用std::function进行保存,并延迟调用到任何我们需要的时候。通过std::bind,可以在调用函数时提前绑定部分参数,从而简化函数调用的过程,这种方式也被称为“柯里化”(currying),有助于提高代码的可读性和灵活性
std::bind通常有两大作用
- 将可调用对象与参数一起绑定为另一个std:.function供调用
- 提前绑定部分参数,简化函数调用
function与bind使用代码
#include<iostream>
using namespace std;int add(int a,int b)
{return a + b;
}
//&add 可调用对象
//function 函数封装器,存储可调用对象
//bind 将可调用对象和部分参数绑定在一起
#include<functional>int main()
{std::function<int(int,int)>f = &add;cout<<"res:"<<f(3,4)<<endl;// res:7//在调用之前 将参数与函数指针捆绑 然后使用function存储//因为已经给定参数 所以类型参数列表int()中为空//因为捆绑的时候已经传完参数 所以调用时不需要传参std::function<int()>f1 = std::bind(&add,3,4);cout<<"res:"<<f1()<<endl;// res:7//当捆绑时不知道需要传递的参数时,可以使用std::placeholders::_1 进行参数占位std::function<int(int ,int)>f2 = std::bind(&add,std::placeholders::_1,std::placeholders::_2);cout<<"res:"<<f2(3,4)<<endl;// res:7return 0;
}
捆绑普通函数
#include<iostream>
using namespace std;int add(int a,int b)
{return a + b;
}#include<functional>//加减乘除都可以实现计算
int getResult(std::function<int(int,int)>f)
{int a = 0,b = 0;cin >> a >> b;return f(a,b);
}
//getResult() 是回调函数,先将函数指针传入(可能包含参数),在需要的时候执行int main()
{std::function<int(int ,int)>f = std::bind(&add,std::placeholders::_1,std::placeholders::_2);int res = getResult(f);cout<<"res:"<<res<<endl;return 0;
}
捆绑类成员函数
#include<iostream>
using namespace std;class AA
{
public:int multi(int a,int b){return a * b;}
};#include<functional>//加减乘除都可以实现计算
int getResult(std::function<int(int,int)>f)
{int a = 0,b = 0;cin >> a >> b;return f(a,b);
}int main()
{AA aa;//将类成员函数指针与对象的地址捆绑(类中非静态成员函数第一个参数默认为this指针)//然后multi还有两个参数 int intstd::function<int(int ,int)>f = std::bind(&AA::multi,&aa,std::placeholders::_1,std::placeholders::_2);int res = getResult(f);cout<<"res:"<<res<<endl;return 0;
}
捆绑静态成员函数
#include<iostream>
using namespace std;class AA
{
public:int multi(int a,int b){return a * b;}static int div(int a,int b){return a / b;}
};#include<functional>//加减乘除都可以实现计算
int getResult(std::function<int(int,int)>f)
{int a = 0,b = 0;cin >> a >> b;return f(a,b);
}int main()
{std::function<int(int ,int)>f =std::bind(&AA::div,std::placeholders::_1,std::placeholders::_2);int res = getResult(f);cout<<"res:"<<res<<endl;return 0;
}
使用function与bind有什么好处?
可以在使用类成员函数时不依赖对象,提前将对象地址绑定到函数指针上
chrono时间库
C++11有了chrono时间库,可以在不同系统中很容易的实现定时功能。
要使用chrono库,需要#include<chrono>,其所有实现均在std::chrono
chrono是一个模版库,使用简单,功能强大,只需要理解三个概念:duration、time_point、clock
#include<iostream>
#include<chrono>
#include<thread>
using namespace std;int main()
{//时间间隔举例std::this_thread::sleep_for(std::chrono::milliseconds(2000));//std::this_thread::sleep_for(std::chrono::seconds(2));cout<<"sleep finish"<<endl;//this_thread代表当前线程 sleep_for休眠 参数传的是chrono的时间间隔//milliseconds 毫秒 seconds 秒/*chrono::millisecondsmilliseconds 类型别名typedef duration<int64_t, milli> milliseconds;typedef ratio<1,1000> milli;ratio<1,1000> 分数 1/1000 因为要使用ms 所以是1s的1/1000chrono::duration 结构体 //模版参数 第一个是数值 第二个是单位std::chrono::milliseconds(2000)std::chrono::duration<int64_t, milli> 2000是int64_t类型的数值*///时间点 打印当前时间std::chrono::system_clock::time_point tp =std::chrono::system_clock::now(); // 获取当前时间点time_t t_t = std::chrono::system_clock::to_time_t(tp);tm* t = std::localtime(&t_t);// string ti = ctime(&t_t);
// cout << ti << endl;//获取毫秒auto du = tp.time_since_epoch();//获取Unix时间戳(时间间隔) 单位是秒//duration 时间间隔转换 duration_cast<转换的目标类型>(原值)int ms = chrono::duration_cast<chrono::microseconds>(du).count();ms = ms % 1000;//Unix时间戳 1970 01 01 00:00:00 起始时间(纪元时间)//1970 01 01 00:00:00的格林威治时间 是纪元时间 epoch//例:格林威治时间 00:00:00 对应北京时间 08:00:00 那么此时北京时间为 1970 01 01 08:00:00//当前时间距离纪元时间有多少秒表示时间戳/*时间戳的应用:例如,网站向服务器发送请求,某些服务器为避免重复指令一直发送,服务器通常会进行过滤,即服务器在短时间内如果收到多次重复命令,就进行过滤。但某些情况下,我们就想要获取服务器响应,那我们就加时间戳,让服务器直到我们想获得当前时效的内容,这时候服务器就进行处理。*///打印当前时间printf("%d-%02d-%02d %02d:%02d:%02d %3d\n",t->tm_year +1900/*年份从1900开始*/,t->tm_mon +1/*月份从0开始*/,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec,ms);//计算一段时间间隔 使用稳定时钟std::chrono::steady_clock::time_point begin =chrono::steady_clock::now();std::this_thread::sleep_for(chrono::seconds(1));std::chrono::steady_clock::time_point end =chrono::steady_clock::now();//两个时间点的差 即为时间间隔auto d = end - begin;//转换为以ms为单位int diff = chrono::duration_cast<chrono::milliseconds>(d).count();cout<<"diff:"<<diff<<endl;return 0;}
时间间隔 duration
chrono::milliseconds
milliseconds 类型别名
typedef duration<int64_t, milli> milliseconds;
typedef ratio<1,1000> milli;
ratio<1,1000> 分数 1/1000 因为要使用ms 所以是1s的1/1000
chrono::duration 结构体 //模版参数 第一个是数值 第二个是单位
std::chrono::milliseconds(2000)
std::chrono::duration<int64_t, milli> 2000是int64_t类型的数值
- 由于各种时间段(duration)表示不同,chrono库提供了duration_cast类型转换函数,用于将duration转换成另一个类型的duration。(例:s转换成ms)
- duration还有一个成员函数count(),用来表示这一段时间的长度
时间点 time_point
获取当前时间点
std::chrono::system_clock::time_point tp = std::chrono::system_clock::now();
注:一个time_point必须有一个clock计时
时钟
system_clock
- 系统时钟,用于获取系统时间的时钟,由于系统时间可以改变,这个时钟也会随之改变
- 不是连续的,一般不用系统时钟计算时间间隔
steady_clock
- 稳定时钟,不受系统时间的影响,是单调递增的,一般用于计算时间间隔
high_resolution_clock
- 高分辨率时钟,system_clock 或 steady_clock 的别名,到底是哪个受系统影响,提供高分辨率的时间计算
三种时钟总结:
- 这三种时钟类都提供了一个静态成员函数now()用于获取当前时间,该函数的返回值是一个time_point类型。
- system_clock除了now()函数外,还提供了to_time_t()静态成员函数。用于将系统时间转换成熟悉的std::time_t类型,得到了time_t类型的值,再使用ctime()函数将时间转换成字符串格式,就可以很方便地打印当前时间了。