您的位置:首页 > 游戏 > 游戏 > C/C++复习 day3(C++11,stl)

C/C++复习 day3(C++11,stl)

2024/12/23 12:31:26 来源:https://blog.csdn.net/m0_52420323/article/details/141158119  浏览:    关键词:C/C++复习 day3(C++11,stl)

C/C++复习day3


文章目录

  • C/C++复习day3
  • 前言
  • 一、C++ 11
    • 1.右值引用
      • push和emplace系列的区别
    • 2.lambda函数
      • 1.用法
        • a. [capture-list]
        • b.parameters
        • c.mutable->
        • d.return-type
        • e.statement
    • 3.包装器
      • 1.function包装器
        • 用法
      • 2.bind函数包装器(适配器)
    • 4.智能指针
      • 1.发展历史
      • 2.RAII
      • 3.各自的特点即模拟实现
        • a.auto_ptr
        • b.unique_ptr
        • c.shared_ptr
          • 循环引用问题
        • d.weak_ptr
      • 4.定制删除器
    • 5.类型转化
      • a.static_cast
      • b.reinterpred_cast
      • c.const_cast
      • d.dynamic_cast
      • RTTI
  • 二、stl
  • 总结


前言

继续day3


一、C++ 11

C++11 引入了很多新奇的东西,帮助我们去更好的结构化编程。

1.右值引用

day01
因为右值引用在day1已经做过梳理,在此不过多赘述。

push和emplace系列的区别

  1. push:可能会接受到一个已经创建好的对象。那么在传参时会调用拷贝构造函数引起不必要的开销。
  2. emplace:接受构造函数的参数(可能为左值也可能为右值),直接在容器内部原位构造对象,避免了不必要的临时对象创建以及可能的复制或移动操作。

2.lambda函数

1.用法

lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement
}

a. [capture-list]

捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。

  1. [var]:表示值传递方式捕捉变量var
  2. [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  3. [&var]:表示引用传递捕捉变量var
  4. [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  5. [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表示函数的形参类型。

std::function<double(double)> func = [](double d)->double{ return d /
4; };cout << useF(func, 11.11) << endl;

这样我们在处理括号匹配就可以通过一个map存储右括号以及对应的匹配函数。

2.bind函数包装器(适配器)

std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。

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)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:

  1. 不需要显式地释放资源。
  2. 采用这种方式,对象所需的资源在其生命期内始终保持有效。

3.各自的特点即模拟实现

a.auto_ptr

作为最早的一个智能指针,它首先提出将指针封装到一个类中,这样调用类对象的构造和析构就可以完成指针的初始化和释放。同时通过重载*,->运算符来真正的模拟实现指针。
但是 auto_ptr 在进行赋值时,会将原有的指针释放并将其置为nullptr,导致delete出现问题。(即管理权转移的思想)
接下来进行简单的模拟实现:

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的问题。

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对象之间共享资源。

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看起来是非常靠谱的,但有一种情况它会出现问题。

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即可解决问题。

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,但它不能用于两个不相关的类型进行转换

int main()
{double d = 12.34;int a = static_cast<int>(d);cout<<a<<endl;return 0;
}

b.reinterpred_cast

reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型

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属性,方便赋值

	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:

  1. typeid运算符
  2. dynamic_cast运算符
  3. decltype(自动推导表达式的类型)

二、stl

在这里插入图片描述
空间配置器:和一个哈希桶一样,小于128就去桶里找,大于128就去malloc

总结

以上就是C/C++的学习笔记,之后可能还会继续补充。
本人小白一枚,有错误之处还望各位大佬指正。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com