C/C++复习day3
文章目录
- C/C++复习day3
- 前言
- [一、C++ 11](#一、C++ 11)
-
- 1.右值引用
- 2.lambda函数
-
- 1.用法
-
- [a. [capture-list]](#a. [capture-list])
- b.parameters
- c.mutable->
- d.return-type
- e.statement
- 3.包装器
- 4.智能指针
- 5.类型转化
- 二、stl
- 总结
前言
继续day3
一、C++ 11
C++11 引入了很多新奇的东西,帮助我们去更好的结构化编程。
1.右值引用
因为右值引用在day1已经做过梳理,在此不过多赘述。
push和emplace系列的区别
- push:可能会接受到一个已经创建好的对象。那么在传参时会调用拷贝构造函数引起不必要的开销。
- emplace:接受构造函数的参数**(可能为左值也可能为右值)**,直接在容器内部原位构造对象,避免了不必要的临时对象创建以及可能的复制或移动操作。
2.lambda函数
1.用法
lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement
}
a. [capture-list]
捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
- [var]:表示值传递方式捕捉变量var
- [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
- [&var]:表示引用传递捕捉变量var
- [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
- [this]:表示值传递方式捕捉当前的this指针
b.parameters
函数参数列表
c.mutable->
默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
d.return-type
返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
e.statement
函数体
注意:lambda函数表达式不能进行互相赋值,即使类型相同
3.包装器
1.function包装器
function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。
上述我门提到lambda函数不能进行赋值(也不能通过函数指针去访问它),那如果我们想将lambda函数赋予一个"名字"怎么办呢? 我们就可以利用function包装器。
用法
function<a(b)>
a表示函数的返回值
b表示函数的形参类型。
cpp
std::function<double(double)> func = [](double d)->double{ return d /
4; };
cout << useF(func, 11.11) << endl;
这样我们在处理括号匹配就可以通过一个map存储右括号以及对应的匹配函数。
2.bind函数包装器(适配器)
std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来"适应"原对象的参数列表。
cpp
int func(int a,int b)
{
cout<<a<<' '<<b<<endl;
}
int main()
{
function<int(int,int)> f1 =
std::bind(func,placeholders::_1,placeholders::_2);
}
f1(1,2);
注意参数默认是从_1开始的,我们可以变化_1,_2的值来调整参数的位置。
4.智能指针
1.发展历史
早期,在C++98标准引入了第一个智能指针:auto_ptr,但其存在一些局限性,权限的转移存在一些问题。
C++11则引入了更加强大的智能指针:unique_ptr,shared_ptr,weak_ptr
2.RAII
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
- 不需要显式地释放资源。
- 采用这种方式,对象所需的资源在其生命期内始终保持有效。
3.各自的特点即模拟实现
a.auto_ptr
作为最早的一个智能指针,它首先提出将指针封装到一个类中,这样调用类对象的构造和析构就可以完成指针的初始化和释放。同时通过重载*,->运算符来真正的模拟实现指针。
但是 auto_ptr 在进行赋值时,会将原有的指针释放并将其置为nullptr,导致delete出现问题。(即管理权转移的思想)
接下来进行简单的模拟实现:
cpp
template<class T>
class auto_ptr{
public:
auto_ptr(T* ptr)
:_ptr(ptr)
{}
auto_ptr(auto_ptr& ap)
:_ptr(ap._ptr)
{
ptr = nullptr;
}
auto_ptr<T>& operator=(auto_ptr& ap)
{
if (this != &ap)
{
if (_ptr != nullptr)
delete _ptr;
}
_ptr = ap._ptr;
ap._ptr = nullptr;
return *this;
}
~auto_ptr()
{
if (_ptr != nullptr)
{
std::cout << "delete _ptr" << std::endl;
delete _ptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
结论:auto_ptr是一个失败设计,很多公司明确要求不能使用auto_ptr
b.unique_ptr
unique_ptr的实现原理:简单粗暴的防拷贝。它直接将拷贝构造和拷贝赋值删除,以此来处理auto_ptr的问题。
cpp
template<class T>
class unique_ptr{
public:
unique_ptr( T* ptr)
:_ptr(ptr)
{}
~unique_ptr()
{
if (_ptr != nullptr)
{
std::cout << "delete _ptr" << std::endl;
delete _ptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
unique_ptr(const unique_ptr<T>& up) = delete;
unique_ptr<T> operator=(const unique_ptr<T>& up) = delete;
private:
T* _ptr;
};
c.shared_ptr
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
cpp
template<class T>
class shared_ptr{
public:
shared_ptr(T* ptr)
:_ptr(ptr)
{
_num = new int(1);
}
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr),
_num(sp._num)
{
(*_num)++;
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
if (this == &sp)
return *this;
if (--*(_num) == 0)
{
std::cout << "delete _ptr" << std::endl;
delete _ptr;
delete _num;
}
_ptr = sp._ptr;
_num = sp._num;
(*_num)++;
return *this;
}
~shared_ptr()
{
if (--(*_num) == 0 && _ptr!=nullptr)
{
std::cout << "delete _ptr" << std::endl;
delete _ptr;
delete _num;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
int use_num()
{
return *(_num);
}
T* get()const
{
return _ptr;
}
private:
T* _ptr;
int* _num;
};
shared_ptr会在成员变量中存一个计数指针,初始化时为其开好空间并且赋值为1,每增加一次引用则让计数指针++。析构时则让其--,判断是否为0,若为0则delete;
循环引用问题
shared_ptr看起来是非常靠谱的,但有一种情况它会出现问题。
cpp
struct ListNode
{
int _data;
shared_ptr<ListNode> _prev;
shared_ptr<ListNode> _next;
~ListNode(){ cout << "~ListNode()" << endl; }
};
int main()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
node1->_next = node2;
node2->_prev = node1;
return 0;
}
问题分析:
node1 和 node2 两个对象指向两个结点,此时引用计数都为1;
但是当我们让node1->_next=node2时,相当于node2多加了一个引用,因为底层的计数指针是互用的,所以此时node2引用计数变为2。同理,node1的也变为2。
然后他俩调用析构函数,引用计数变为1。但_next和_pre还指向的结点,因此都无法完成析构。这就叫循环引用。
为了解决这个问题,我们就引入了weak_ptr
d.weak_ptr
特点:在引用时,内部不会增加引用计数,同时析构函数也不会去释放指针。
因此将上述next和pre指针换为weak_ptr即可解决问题。
cpp
template<class T>
class weak_ptr{
public:
weak_ptr(T* ptr=nullptr)
:_ptr(ptr)
{}
weak_ptr(const shared_ptr<T>& sp)
:_ptr(sp.get())
{}
weak_ptr<T>& operator=(const shared_ptr<T>& sp)
{
_ptr = sp.get();
return *this;
}
~weak_ptr()
{
std::cout << "~weak_ptr" << std::endl;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
4.定制删除器
如果对象不是new出来的,而是malloc出来的该怎么办呢?
这里通过一个删除器来解决,即对智能指针传一个仿函数进去。
new->delete || new[] -> delete [] || malloc -> free
5.类型转化
标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:
static_cast、reinterpret_cast、const_cast、dynamic_cast
a.static_cast
static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast,但它不能用于两个不相关的类型进行转换
cpp
int main()
{
double d = 12.34;
int a = static_cast<int>(d);
cout<<a<<endl;
return 0;
}
b.reinterpred_cast
reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型
cpp
int a=0;
// 这里使用static_cast会报错,应该使用reinterpret_cast
//int *p = static_cast<int*>(a);
int *p = reinterpret_cast<int*>(a);
c.const_cast
const_cast最常用的用途就是删除变量的const属性,方便赋值
cpp
const int a = 0;
int* p = const_cast<int*>(&a);
*p = 2;
std::cout << a << " " << *p << std::endl;
// 不会改变原来a的值
d.dynamic_cast
dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)
向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)
注意:
1. dynamic_cast只能用于父类含有虚函数的类
2. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0
RTTI
RTTI:Run-time Type identification的简称,即:运行时类型识别。
C++通过以下方式来支持RTTI:
- typeid运算符
- dynamic_cast运算符
- decltype(自动推导表达式的类型)
二、stl
空间配置器:和一个哈希桶一样,小于128就去桶里找,大于128就去malloc
总结
以上就是C/C++的学习笔记,之后可能还会继续补充。
本人小白一枚,有错误之处还望各位大佬指正。