泛型
泛型就是把数据类型也写成参数,让同一段代码可以安全地处理不同类型,实现代码复用和编译期类型安全。
核心思想就是:不针对具体类型写代码,而是针对任意满足约束的类型T写逻辑。并且能够保证类型安全,在编译期就检查类型是否合法,不会出现错误强制类型转换
c++泛型当中主要通过函数模版和类模版来实现
函数模版
cpp
template<typename T>
T add(T a,T b)
{
return a+b;
}
//使用
int x=add<int>(1,8);
double y=add<double>(1.1,2.3);
类模板
cpp
template<typename T>
class MyStack
{
private:
T data[100];
int top;
public:
void push(T val)
{
data[++top]=val;
}
};
//使用
MyStack<int> st;
c++11新特性
智能指针
智能指针会帮助我们管理动态分配的内存,会自动帮助我们释放new出来的内存,避免内存泄漏
std::unique_ptr
独占所有权,最常用
特性:两个unique_ptr不指向同一个资源,所以他禁止复制,只允许转移所有权。
无法左值复制赋值操作,但允许临时右值构造和赋值
左值就是有名字,能赋值,能够反复用的变量
cpp
std::unique_ptr<int> p1(new int(10));
p1就是左值 ,现在我们来赋值构造
cpp
std::unique_ptr<int> p2(p1);
这就是赋值构造,但是编译器不让你干,p1和p2指向同一块内存,析构会重复释放。
复制构造也不行
cpp
std::unique_ptr<int> p2;
p2 = p1; // 报错!
但是允许临时右值的构造赋值
右值就是临时对象,用完就销毁,
允许右值构造,
cpp
std::unique_ptr<int> p=std::unique_ptr<int>(10);
允许右值赋值,和转移所有权
cpp
std::unique_ptr<int> p1=std::unique_ptr<int>(10);
std::unique_ptr<int> pw;
p2=std::move(p1);
shared_ptr
共享内存
有三条规则:1 拷贝/赋值共享内存,引用计数+1
2指针销毁/置空,引用计数-1
3计数等于0,释放内存
cpp
shared_ptr<int> p1=make_shared<int>(10);//计数为1
shared_ptr<int> p2=p1;//计数为2
p1.reset();//重置指针,计数减1
p2.reset();//计数0,自动释放内存
weak_ptr
是弱引用指针,不拥有对象所有权,也不强加计数,作用是打破shared_ptr的循环引用。
weak_ptr除了循环引用问题,还解决了什么问题呢?
1解决了野指针/悬空的问题
cpp
A* p=new A();
delete p;
p->func();//此时p还保存着地址,但是对象已经没了。崩溃,访问悬空指针
如果用一个普通指针观察shared_ptr管理的对象
cpp
shared_ptr<A> sp=make_shard<A>();
A* raw=sp.get();//拿到裸指针
sp.reset();//对象被销毁
raw->func();//悬空指针,崩溃
weak_ptr会感知对象是否存活,不会变成野指针
cpp
shared_ptr<A> sp = make_shared<A>();
weak_ptr<A> wp = sp;
// 检查对象是否还活着
if (!wp.expired()) {
// 安全访问
}
sp.reset(); // 对象销毁
// 现在 wp 知道对象没了
if (wp.expired()) {
cout << "对象已销毁,不会访问野指针" << endl;
}
解决this指针不能安全生成shared_ptr的问题
cpp
struct A {
shared_ptr<A> getSelf() {
return shared_ptr<A>(this); // 灾难!
}
};
shared_ptr<A> sp1 = make_shared<A>();
auto sp2 = sp1->getSelf();
sp1有一个引用计数块,sp2又有一个引用计数块,两个独立的shared_ptr管理同一个对象,双方都会delete,导致重复释放资源。
std::enable_shared_from_this 底层就是用 weak_ptr 保存 this
右值引用
右值引用专门来绑定右值
cpp
int& lref=a;
int&& rref=10;
右值引用的核心作用:实现移动语义
传统拷贝构造函数会做深拷贝,复制堆上的数据,开销极大,并且有些是临时对象,没有必要
cpp
string getStr()
{
return "hello world";
}
string s=getStr();
临时对象马上就要销毁,却还要把数据完整复制一遍,纯纯多余。
右值引用让我们可以接管临时对象的资源,而不是复制
通过右值引用,我们可以实现移动构造和移动赋值
cpp
// 拷贝构造(深拷贝,慢)
MyString(const MyString& other) {
size_t n = strlen(other.data)+1;
data = new char[n];
memcpy(data, other.data, n);
std::cout << "拷贝构造\n";
}
// 移动构造(转移指针,快)
MyString(MyString&& other) noexcept {
data = other.data; // 直接偷指针
other.data = nullptr;// 原对象置空
std::cout << "移动构造\n";
}
lamada表达式
引入的匿名函数,就可以就地定义,就地引用。
lamada表达式的书写格式:
cpp
[capture-list](parameters)mutable->return->type{statement}
capture-list\]捕获列表,决定lamada可以访问哪些外部变量,以什么方式访问 \[ \]空捕获,不使用任何外部变量 \[x\]值捕获,默认只读,不能修改 \[\&x\]默认引用捕获, \[=\]默认值捕获用到的所有外部变量 \[\&\]默认引用捕获所有用到的外部变量 \[this\]类成员函数当中使用,捕获当前对象的this指针,可以访问成员变量或者函数。 \[\&,=a\]默认引用捕获,b用值 mutable 让值捕获的变量从const变成可修改 return-\>type在只有一条语句的时候,可以省略。如果有多条return,并且类型不一样