目录
RAII
C++98:引入STL
C++11:
for_each(v.begin(),v.end(),func)//函数模板,对于每一个vi都调用func函数;
func也可以为LAMBDA表达式,更加方便;
右值引用;
std::move();
C++17:
STL模板参数可省略(CTAD--编译器参数推断)
引入一些常用数值算法
C++20:
引入区间ranges
实现了模块,可以通过import导入,不需要#include
引入协程、format
支持乱序参数
封装:不变性(getter\setter)
RAII(Resource Acquisition Is Initialization)
避免犯错,少用new\delete;
RAII应用:异常安全try\catch,出异常会自动释放;
const成员变量,只允许通过初始化列表进行初始化 ;
explicit关键词表示不能执行隐式参数转换,防止二义性
{}写参数不允许narrow_cast
·当一个类(和他的基类)没有定义任何构造函数,这时编译器会自动生成一个参数个数和成员一样的构造函数。
.他会将{}内的内容,会按順序赋值给对象的每一个成目的是为了方便程序员不必手写冗长的构造函数一个个赋值给成员。
·不过初始化列表的构造函数只支持通过{}或={}来构造,不支持通过()构造。其实是为了向下兼容C++98
写类基础类型成员变量时:
最好int x{0}或int x=0这样写,防止引调用默认构造函数给成员变量赋随机值
多返回值实现方法
1.利用构造函数实现
struct HitRes{
bool hit;
int vec;
float depth
}
HitRes intersect(Ray r)
{
...
return{true,1,3.01};//返回一个匿名对象;
}
int main(){
Ray r;
auto hit=intersect(r);
...
}
2.利用传引用参数实现
#include<iostream>
using namespace std;
int decrease(int x,bool &state){
x = x-1;
state=true;
return x;
}
int main(){
int x=1;
bool state=false;
std::cout<<"Before function is called: "<<x<<" "<<state<<endl;
decrease(x,state);
std::cout<<"After function is called: "<<x<<" "<<state<<endl;
return 0;
}
拷贝与移动
Temp()=default;//恢复生成一个默认构造函数
再次注意:对于成员函数存在指针的情况时的拷贝构造函数,防止浅拷贝!!!
C++为什么区分拷贝与移动
·有时候,我们需要把一个对象v2移动到v1上。而不需要涉及实际数据的拷贝。
·时间复杂度:移动是O(1),拷贝是O(n)。
·我们可以用std::move实现移动。
·v2的内存空间被移动到vl后,原来的v2对象会被free,因此仅当v2再也用不到时才用移动。
换句话说,移动是直接对内存的接管;
string x = "Hello World!";
std::cout <<"Before move:" << x << endl;;
vector<string>v;
v.push_back(std::move(x));
std::cout << "After move:" << x << endl;
char* p = new char[6];
移动进阶:
·除了std::move可以把v2移动到vl外,
·还可以通过std::swap交换vl和v2 //复杂度O(1)
·swap在高性能计算中可以用来实现双缓存(ping-pongbuffer)。
这些情况下编译器会调用移动(编译器优化):
return v2 //v2作返回值
v1=std::vector<int>(200) //就地构造的v2
v1=std::move(v2) //显式地移动
这些情况下编译器会调用拷贝:
return std::as_const(v2) //显式地拷贝
v1=v2 //默认拷贝
注意,以下语句没有任何作用:
std::move(v2) //不会清空v2,需要清空可以用v2={}(调用了默认构造函数或移动赋值函数)或v2.clear()
std::as_const(v2)//不会拷贝v2,需要拷贝可以用{auto_=v2}
·这两个函数只是负责转换类型,实际产生移动/拷贝效果的是在类的构造值函数里。
自定义移动构造函数\移动赋值函数-可以保证高性能,同时降低时间复杂度!
智能指针
RAII解决内存管理的问题:unique_ptr类
std::unique_ptr<c> p=std::make_unique<c>(); //通过解构函数实现自动释放
new C-随机初始化,new C()-保证0初始化。
旧的C++时代里:
delete p;
p=nullptr;防止p再次被使用造成空悬指针;//提前释放
unique_ptr删除了拷贝构造函数,因此不能作为参数,目的是防止浅拷贝重复释放的问题。
解决方法:1.可以使用p.get()作为参数,不接管原始对象的生命周期
2.使用std::move(p)作为参数
更智能指针:shared_ptr //但是效率不如unique_ptr
解决了unique_ptr无法拷贝的问题,因为其实现了引用计数
.use_count() //返回引用计数
此外share_ptr有可能出现循环引用的问题,导致内存泄漏;
parent->m_child=child;
child->m_parent=parent;
parent=nullptr;//parent不会被释放,因为child还指向他;
child=nullptr;//child不会被释放,因为child还指向他;
解决方法:将其中一个设置为weak_ptr //不影响计数;
初学者可以多使用shared_ptr 和weak_ptr的组合,更加安全。
如何避免不必要的拷贝:
1.const&
将参数定义为常引用来避免拷贝;
2.explicit
在拷贝构造函数之前加入explicit关键词,可防止不必要的隐式类型转换造成的拷贝。
扩展知识:
P-IMPL模式
虚函数与纯虚函数
拷贝如何作为虚函数
std::unique_ptr::release()
std::enable_sharedfrom_this
dynamiccast
std::dynamic_pointer_cast
运算符重载
右值引用&&