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

C/C++复习day3


文章目录


前言

继续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表示函数的形参类型。

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)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:

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

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:

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

二、stl

空间配置器:和一个哈希桶一样,小于128就去桶里找,大于128就去malloc

总结

以上就是C/C++的学习笔记,之后可能还会继续补充。

本人小白一枚,有错误之处还望各位大佬指正。

相关推荐
2401_8574396913 分钟前
SSM 架构下 Vue 电脑测评系统:为电脑性能评估赋能
开发语言·php
SoraLuna41 分钟前
「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台
开发语言·macos·ui·华为·harmonyos
xlsw_1 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
Dream_Snowar2 小时前
速通Python 第三节
开发语言·python
唐诺2 小时前
几种广泛使用的 C++ 编译器
c++·编译器
XH华2 小时前
初识C语言之二维数组(下)
c语言·算法
高山我梦口香糖3 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
冷眼看人间恩怨3 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
信号处理学渣3 小时前
matlab画图,选择性显示legend标签
开发语言·matlab
红龙创客3 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++