C++ 智能指针

C++ 智能指针

  • 为什么需要智能指针?
  • auto_ptr
  • unique_ptr
  • shared_ptr
  • weak_ptr
  • 智能指针的核心实现
    • unique_ptr的简单实现
    • Counter的简单实现
    • share_ptr的简单实现
    • weak_ptr简单实现
  • shared_ptr的线程安全性
  • 多线程无保护读写 shared_ptr 可能出现的问题
  • make_shared()
  • share_ptr/unique_ptr自定义删除器

为什么需要智能指针?

如果指针忘记调用delete,将会造成内存泄漏。当超出了智能指针类的作用域时,智能指针类会自动调用析构函数,析构函数会自动释放资源

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果

C++STL共提供了四个智能指针: auto_ptr, unique_ptr,shared_ptr, weak_ptr 其中C++11只支持后三个,C++98支持所有四个。

auto_ptr

auto_ptr采用独占式拥有模式,下面有一个例子:

auto_ptr<string> p1 (new string ("I reigned lonely as a cloud.")); 
auto_ptr<string> p2; 
p2 = p1; //auto_ptr不会报错.

当程序运行时访问p1将会报错,因为所有权从p1转让给了p2,此时p1不再拥有该字符串对象从而变成空指针。

故auto_ptr的缺点是:存在潜在的内存崩溃问题!

unique_ptr

unique_ptr用于替换auto_ptr,实现了独占式拥有概念,保证同一时间内只有一个智能指针可以指向该对象。为此,unique_ptr的拷贝构造和拷贝赋值均被声明为delete。因此无法实施拷贝和赋值操作,但可以移动构造和移动赋值。它对于避免资源泄露(例如“以new创建对象后因为发生异常而忘记调用delete”)特别有用,还是上面那个例子:

unique_ptr<string> p3 (new string ("auto"));   //#4
unique_ptr<string> p4;                       //#5
p4 = p3;//此时会报错!!

编译器认为p4=p3非法,避免了p3不再指向有效数据的问题。尝试复制p3时会编译期出错,而auto_ptr能通过编译期从而在运行期埋下出错的隐患。因此,unique_ptr比auto_ptr更安全。

另外unique_ptr还有更聪明的地方:当程序试图将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器将禁止这么做,比如:

unique_ptr<string> pu1(new string ("hello world")); 
unique_ptr<string> pu2; 
pu2 = pu1;                                      // #1 不允许
unique_ptr<string> pu3; 
pu3 = unique_ptr<string>(new string ("You"));   // #2 允许

其中#1留下悬挂的unique_ptr(pu1),这可能导致危害。而#2不会留下悬挂的unique_ptr,因为它调用 unique_ptr 的构造函数,该构造函数创建的临时对象在其所有权让给 pu3 后就会被销毁。这种随情况而已的行为表明,unique_ptr 优于允许两种赋值的auto_ptr 。

如果确实想执行类似与#1的操作,C++有一个标准库函数std::move(),让你能够将一个unique_ptr赋给另一个。例如:

unique_ptr<string> ps1, ps2;
ps1 = demo("hello");
ps2 = move(ps1);
ps1 = demo("alexia");
cout << *ps2 << *ps1 << endl;

shared_ptr

shared_ptr实现共享式拥有概念。通过引用计数,多个智能指针可以指向相同对象,该对象和其相关资源会在最后一个引用被销毁时候释放

它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。

当复制一个shared_ptr,引用计数会+1。当我们调用release()或者当一个shared_ptr离开作用域时,计数减1(普通的指针如果没有delete操作,离开作用域时并不会被释放,只有在进程结束后才会被释放)。当计数等于0时,则delete内存

成员函数:

  • use_count :返回引用计数的个数
  • unique :返回是否是独占所有权( use_count 为 1)
  • swap :交换两个 shared_ptr 对象(即交换所拥有的对象)
  • reset :放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少
  • get :返回内部对象(指针)

share_ptr的简单例子:

int main()
{
	string *s1 = new string("s1");

	shared_ptr<string> ps1(s1);
	shared_ptr<string> ps2;
	ps2 = ps1;

	cout << ps1.use_count()<<endl;	//2
	cout<<ps2.use_count()<<endl;	//2
	cout << ps1.unique()<<endl;	//0

	string *s3 = new string("s3");
	shared_ptr<string> ps3(s3);

	cout << (ps1.get()) << endl;	//033AEB48
	cout << ps3.get() << endl;	//033B2C50
	swap(ps1, ps3);	//交换所拥有的对象
	cout << (ps1.get())<<endl;	//033B2C50
	cout << ps3.get() << endl;	//033AEB48

	cout << ps1.use_count()<<endl;	//1
	cout << ps2.use_count() << endl;	//2
	ps2 = ps1;
	cout << ps1.use_count()<<endl;	//2
	cout << ps2.use_count() << endl;	//2
	ps1.reset();	//放弃ps1的拥有权,引用计数的减少
	cout << ps1.use_count()<<endl;	//0
	cout << ps2.use_count()<<endl;	//1
}

share_ptr的缺点为:当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏,此时需要使用weak_ptr。

weak_ptr

weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放;它是对对象的一种弱引用,不会增加对象的引用计数;它和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。

class B;	//声明
class A
{
public:
	shared_ptr<B> pb_;
	~A()
	{
		cout << "A delete\n";
	}
};

class B
{
public:
	shared_ptr<A> pa_;
	~B()
	{
		cout << "B delete\n";
	}
};

void fun()
{
	shared_ptr<B> pb(new B());
	shared_ptr<A> pa(new A());
	cout << pb.use_count() << endl;	//1
	cout << pa.use_count() << endl;	//1
	pb->pa_ = pa;
	pa->pb_ = pb;
	cout << pb.use_count() << endl;	//2
	cout << pa.use_count() << endl;	//2
}

int main()
{
	fun();
	return 0;
}

可以看到fun函数中pa ,pb之间互相引用,两个资源的引用计数为2,当要跳出函数时,智能指针pa,pb析构时两个资源引用计数会减1,但是两者引用计数还是为1,导致跳出函数时资源没有被释放(A、B的析构函数没有被调用)运行结果没有输出析构函数的内容,造成内存泄露。如果把其中一个改为weak_ptr就可以了,我们把类A里面的shared_ptr pb_,改为weak_ptr pb_ ,运行结果如下:

1
1
1
2
B delete
A delete

这样的话,资源B的引用开始就只有1,当pb析构时,B的计数变为0,B得到释放,B释放的同时也会使A的计数减1,同时pa析构时使A的计数减1,那么A的计数为0,A得到释放。

注意:我们不能通过weak_ptr直接访问对象的方法,比如B对象中有一个方法print(),我们不能这样访问,pa->pb_->print(),因为pb_是一个weak_ptr,应该先把它转化为shared_ptr,如:

shared_ptr<B> p = pa->pb_.lock();
p->print();

weak_ptr 没有重载*和->运算符,但可以使用 lock 获得一个可用的 shared_ptr 对象. 注意, weak_ptr 在使用前需要检查合法性.

成员函数:

  • expired :用于检测所管理的对象是否已经释放, 如果已经释放, 返回 true; 否则返回 false.
  • lock :用于获取所管理的对象的强引用(shared_ptr). 如果 expired 为 true, 返回一个空的 - - - shared_ptr; 否则返回一个 shared_ptr, 其内部对象指向与 weak_ptr 相同.
  • use_count :返回与 shared_ptr 共享的对象的引用计数.
  • reset :将 weak_ptr 置空.
  • weak_ptr :支持拷贝或赋值, 但不会影响对应的 shared_ptr 内部对象的计数.

智能指针的核心实现

unique_ptr的简单实现

简单的实现了unique_ptr,包括如下成员函数:

  • 构造函数
  • 析构函数
  • 拷贝构造函数,禁用,不支持
  • 拷贝赋值函数,禁用,不支持
  • reset():释放源资源,指向新资源
  • release():返回资源,放弃对资源的管理
  • get():返回资源,只是供外部使用,依然管理资源
  • operator bool (): 是否持有资源
  • operator * ()
  • operator -> ()
template<typename T>
class UniquePtr
{
public:
	UniquePtr(T *pResource = NULL)
		: m_pResource(pResource)
	{

	}

	~UniquePtr()
	{
		del();
	}

public:
	void reset(T* pResource) // 先释放资源(如果持有), 再持有资源
	{
		del();
		m_pResource = pResource;
	}

	T* release() // 返回资源,资源的释放由调用方处理
	{
		T* pTemp = m_pResource;
		m_pResource = nullptr;
		return pTemp;
	}

	T* get() // 获取资源,调用方应该只使用不释放,否则会两次delete资源
	{
		return m_pResource;
	}

public:
	operator bool() const // 是否持有资源
	{
		return m_pResource != nullptr;
	}

	T& operator * ()
	{
		return *m_pResource;
	}

	T* operator -> ()
	{
		return m_pResource;
	}

private:
	void del()
	{
		if (nullptr == m_pResource) return;
		delete m_pResource;
		m_pResource = nullptr;
	}

private:
	UniquePtr(const UniquePtr &) = delete; // 禁用拷贝构造
	UniquePtr& operator = (const UniquePtr &) = delete; // 禁用拷贝赋值

private:
	T *m_pResource;
};

Counter的简单实现

为了实现weak_ptr和share_ptr的引用计数,首先先实现一个Counter计数器类。

Counter对象的目地就是用来申请一个块内存来存引用基数,s是share_ptr的引用计数,w是weak_ptr的引用计数,当w为0时,删除Counter对象。

class Counter
{
public:
    Counter() : s(0), w(0){};
    int s;	//share_ptr的引用计数
    int w;	//weak_ptr的引用计数
};

share_ptr的简单实现

share_ptr的给出的函数接口为:构造,拷贝构造,赋值,解引用,通过release来在引用计数为0的时候删除_ptr和cnt的内存,各个函数中计数器的变化如下:

  1. 构造函数中计数初始化为1;
  2. 拷贝构造函数中计数值加1;
  3. 赋值运算符中,左边的对象引用计数减1,右边的对象引用计数加1;
  4. 析构函数中引用计数减1;
  5. 在赋值运算符和析构函数中,如果减1后为0,则调用delete释放对象。
template<class T>
class Weak_ptr;//先引用 

template<class T>
class Share_ptr
{
	T * ptr;//管理的指针 
	Count * cnt;//计数 
	public:
		Share_ptr(T * p = 0):ptr(p)//构造函数 
		{
			ptr = p;
			cnt = new Count();
		}
		Share_ptr(Share_ptr<T> const& s)//拷贝构造函数, 
		{
			ptr=s.ptr;//对象切换 
			cnt=s.cnt;//值切换 
			cnt->s++;//share计数增加 
		}
		Share_ptr(Weak_ptr<T> const& w)
		{
			ptr = w.ptr;
			cnt = w.cnt;
			cnt->s++; //share计数增加 
		}
		~Share_ptr()//离开生命周期时调用 
		{
			release();//清空内存 
		}
		Share_ptr<T> &operator=(Share_ptr<T> const& s)//赋值运算 
		{
			if(this != &s)//判断是否是相等的指针如果不是 
			{
				release();//释放原有的 
				ptr=s.ptr;//修改对象指针 
				cnt=s.cnt;//修改计数器 
				cnt->s++;//计数器增加 
			}
			return *this;//返回 
		}
		T& operator*()
		{
			*ptr;//解引用 
		}
		
		T* operator->()
		{
			return ptr;//返回原指针 
		} 
		
		friend class Weak_ptr<T>;//方便Weak指针操作本类 
		
	protected:
		void release()//释放操作 
		{
			cnt->s--;//share计数减1 
			if(cnt->s<1)//如果小于一了,说明没有指向原指针的share指针 
			{
				delete ptr;//删除ptr,调用ptr的析构函数 
				if(cnt->w<1)//如果weak引用小于1了再去删除cnt 
				{
					delete cnt;
					cnt=NULL;
				}
			}
		}
		
};

weak_ptr简单实现

weak_ptr的作为弱引用指针,其实现依赖于counter的计数器类和share_ptr的赋值。

weak_ptr一般通过share_ptr来构造,通过expired函数检查原始指针是否为空,lock来转化为share_ptr。

template<class T>
class Weak_ptr
{
	T * ptr;
	Count * cnt;
	public:
		Weak_ptr()//构造函数 
		{
			cnt=0;
			ptr=0;
		}
		Weak_ptr(Weak_ptr<T> &w):ptr(w.ptr),cnt(w.cnt)//同上 
		{
			cnt->w++;
		}
		Weak_ptr(Share_ptr<T> &s):ptr(s.ptr),cnt(s.cnt)//同上 
		{
			cnt->w++;
		}
		Weak_ptr<T>& operator=(Weak_ptr<T> &w)//同上 
		{
			if(this!=&w)
			{
				release();
				cnt=w.cnt;
				ptr=w.ptr;
				cnt->w++;
			}
			return *this;
		}
		Weak_ptr<T>& operator=(Share_ptr<T> &s)//同上 
		{
			release();
			cnt=s.cnt;
			ptr=s.ptr;
			cnt->w++;
			return *this;
		}
		Share_ptr<T> lock()//强转 
		{
			return static_cast<Share_ptr<T>>(*this);
		}
		~Weak_ptr()//释放 
		{
			release();
		}
		friend class Share_ptr<T>;
	protected:
		void release()
		{
			cnt->w--;
			if(cnt->s<1&&cnt->w<1)
			{
				//这里的cnt不用删除,因为在share中已经被删了 
				//delete cnt;
				cnt=NULL;	
			}	
		}	
};

shared_ptr的线程安全性

  • 引用计数增加是安全的,引用计数在堆上。
  • 不同线程同时操作同一个shared_ptr的引用是不安全的 。
  • 不同的shared_ptr指向同一块内存,操作同一个内存也是不安全的(即shared_ptr指向对象的读写不是线程安全的)。

shared_ptr 是引用计数型智能指针,几乎所有的实现都采用在堆上放个计数值的办法。具体来说,shared_ptr 包含两个成员,一个是指向 Foo 的指针 ptr,另一个是 ref_count 指针,指向堆上的 ref_count 对象。ref_count 对象有多个成员,具体的数据结构如图 1 所示,其中 deleter 和 allocator 是可选的。

在这里插入图片描述

图 1:shared_ptr 的数据结构。

为了简化并突出重点,后文只画出 use_count:

在这里插入图片描述

以上是 shared_ptr x(new Foo); 对应的内存数据结构。

如果再执行 shared_ptr y = x; 那么对应的数据结构如下:
在这里插入图片描述

但是 y=x 涉及两个成员的复制,这两步拷贝不会同时(原子)发生。

中间步骤 1,复制 ptr 指针:

在这里插入图片描述

中间步骤 2,复制 ref_count 指针,导致引用计数加 1:

在这里插入图片描述

步骤1和步骤2的先后顺序跟实现相关(因此步骤 2 里没有画出 y.ptr 的指向),我见过的都是先1后2。

既然 y=x 有两个步骤,如果没有 mutex 保护,那么在多线程里就有 race condition。

多线程无保护读写 shared_ptr 可能出现的问题

考虑一个简单的场景,有 3 个 shared_ptr 对象 x、g、n:

shared_ptr<Foo> g(new Foo); // 线程之间共享的 shared_ptr
shared_ptr<Foo> x; // 线程 A 的局部变量
shared_ptr<Foo> n(new Foo); // 线程 B 的局部变量

一开始,各安其事。

在这里插入图片描述

线程 A 执行 x = g; (即 read g),以下完成了步骤 1,还没来及执行步骤 2。这时切换到了 B 线程。

在这里插入图片描述

同时编程 B 执行 g = n; (即 write G),两个步骤一起完成了。

先是步骤 1:

在这里插入图片描述

再是步骤 2:

在这里插入图片描述

这时 Foo1 对象已经销毁,x.ptr 成了空悬指针!

最后回到线程 A,完成步骤 2:
在这里插入图片描述

多线程无保护地读写 g,造成了“x.ptr 是空悬指针”的后果。这正是多线程读写同一个 shared_ptr 必须加锁的原因。

make_shared()

智能指针shared_ptr有两种初始化的方式:

shared_ptr<int> sp1 (new int(10));            //通过new构造数据对象,调用了share_ptr的构造函数
shared_ptr<int> sp2 = make_shared<int>(10);  //通过make_shared构造数据对象

之前看过一些相关的文档,描述了这两种方式的不同,主要的区别是说通过new构造的时候:

  • 通过new构造,涉及到两次内存分配,第一次是通过new为数据对象分配内存,即上方的new int(10),第二次是构造一个shared_ptr的管理对象,管理对象记录了强引用(shared_ptr)计数,弱引用(weak_ptr)计数,以及数据对象(new int(10))的地址。当管理对象发现强引用计数为0时,释放数据对象的内存,当管理对象发现弱引用计数为0时,释放管理对象的内存
    在这里插入图片描述

  • 通过make_shared构造,只分配一次内存,这一块内存里既包括管理对象,也包括数据对象。由于是在一块内存里,所以即使强引用计数已被清零,但如果弱引用计数还没有清零,那么也无法释放这一块内存,直到弱引用计数清零时,这一块内存(包括管理对象和数据对象)才能被释放
    在这里插入图片描述

注意:构造函数是保护或私有时,无法使用 make_shared()

share_ptr销毁了,但还有weak_ptr指向那个对象,weak_ptr怎么知道这个对象已销毁?
share_ptr销毁的时候,只是把指向的对象销毁了,而计数器Counter还没被销毁,Counter里面记录了share_ptr的引用计数以及weak_ptr的引用计数,weak_ptr可以通过查询Counter里面的值来知道对象已被销毁。当weak_ptr也小于1时,Counter才会被销毁。

share_ptr/unique_ptr自定义删除器

默认情况下,智能指针使用 delete 释放其管理的资源,有时候,可能要修改默认使用 delete 释放资源的行为,除此之外,我们也可以自定义删除器。

Connection 是一个管理连接类,在释放 Connection 之前,我们需要调用 close 函数来关闭连接。观察如下代码:

#include <iostream>
#include <memory>
#include <string>

using namespace std;

class Connection{
public:
    explicit Connection(string name):_name(name){
    }
    string get_name() const {
        return _name;
    }
private:
    string _name;
};

void close(Connection* connection){
    cout <<string ("关闭")+connection->get_name ()+"管理的连接中..." << endl;
    // 关闭连接的代码
    // .....
    cout << "关闭完成。" << endl;
}

int main(){
    // 新建管理连接 Connection 的智能指针
    shared_ptr<Connection> sp(new Connection);
    unique_ptr<Connection> up(new Connection);
}

执行上述代码,发现并没有办法调用 close 函数,因为控制权完全在 shared_ptr/unique_ptr 中。你可能会说,在退出作用域之前我调用 close (sp.get ()) 先关闭连接,这样不就可以了嘛?实际上,这种做法对于 shared_ptr 并不安全,手动 close 之后,不能确保 sp 管理的 Connection 只有一份拷贝(即 sp 中的计数器多于 1)。因此,需要使用自定义的删除器。

删除函数定义类似于:

void Deleter(Connection *connection){
    close(connection);
    delete connection;
}

当删除器的指针 Deleter 传给 shared_ptr/unique_ptr 时,shared_ptr/unique_ptr 不会使用默认的 delete val 来释放其管理的资源,而是使用 Deleter (val) 来释放资源,这样就调用了 Deleter 来释放管理的资源。后面的各种方式的原理也是如此。

int main(){
    // 新建管理连接 Connection 的智能指针
    shared_ptr<Connection> sp(new Connection("shared_ptr"), Deleter);
    unique_ptr<Connection, decltype(Deleter)*> up(new Connection("unique_ptr"), Deleter);
}

shared_ptr 在使用的时候,只需要把函数式删除器的指针传给构造函数就行;而 unique_ptr 还用增加一个模板参数 decltype (Deleter)*,这是 shared_ptr 和 unique_ptr 的不同点之一。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/59160.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

类文件一些内容

1、类加载 将类的字节码加载到JVM中&#xff0c;并转换为可以被JVM运行的数据结构的过程 类文件结构

8月3日上课内容 LNMP精讲

LNMP&#xff1a;目前成熟的企业网站的应用模式之一&#xff0c;指的是一套协作工作的系统和相关文件 能够提供静态页面服务&#xff0c;也可以提供动态web服务。 这是一个缩写 L linux系统&#xff0c;操作系统。 N nginx网站服务&#xff0c;前端&#xff0c;提供前端的静…

【NLP概念源和流】 01-稀疏文档表示(第 1/20 部分)

一、介绍 自然语言处理(NLP)是计算方法的应用,不仅可以从文本中提取信息,还可以在其上对不同的应用程序进行建模。所有基于语言的文本都有系统的结构或规则,通常被称为形态学,例如“跳跃”的过去时总是“跳跃”。对于人类来说,这种形态学的理解是显而易见的。 在这篇介…

maven下载安装及初次使用相关配置

maven下载按照及初次使用相关配置 一、下载 与安装 下载完解压放在文件夹中即可&#xff01; 依赖Java&#xff0c;需要配置JAVA_HOME设置MAVEN自身的运行环境&#xff0c;需要配置MAVEN_HOME&#xff08;参考安装java&#xff09;测试环境配置结果 MVN测试成功&#xff01…

Eureka增加账号密码认证登录

一、业务背景 注册中心Eureka在微服务开发中经常使用到&#xff0c;用来管理发布的微服务&#xff0c;供前端或者外部调用。但是如果放到生产环境&#xff0c;我们直接通过URL访问的话&#xff0c;这显然是不安全的。 所以需要给注册中心加上登录认证。 通过账号和密码认证进行…

iMX6ULL应用移植 | 移植 infoNES 模拟器(重玩经典NES游戏)

没玩过NES游戏的童年&#xff0c;可能不是80后的童年。我们小时候是从玩FC开始接触游戏机的&#xff0c;那时真的是红极一时啊&#xff0c;我上初中时还省吃俭用买了一台小霸王&#xff0c;暑假里把电视机都给打爆了&#xff01;那时任天堂单是FC机的主机的发售收入就超过全美的…

性能测试jmeter连接数据库jdbc(sql server举例)

一、下载第三方工具包驱动数据库 1. 因为JMeter本身没有提供链接数据库的功能&#xff0c;所以我们需要借助第三方的工具包来实现。 &#xff08;有这个jar包之后&#xff0c;jmeter可以发起jdbc请求&#xff0c;没有这个jar包&#xff0c;也有jdbc取样器&#xff0c;但不能发起…

Sketch打不开AI文件?转换方法在这里

1、对比设计软件 Sketch 与 AI 软件功能 Sketch 与 Illustrator 都是行业内优秀的矢量图形设计软件&#xff0c;各有千秋。Sketch 从 2010 年面世&#xff0c;专注 APP 界面设计&#xff0c;深受初学者与专业人士喜爱。Illustrator 拥有更悠久的历史&#xff0c;是处理复杂图标…

基于dockerfile构建sshd、httpd、nginx、tomcat、mysql、lnmp、redis镜像

一、镜像概述 Docker 镜像是Docker容器技术中的核心&#xff0c;也是应用打包构建发布的标准格式。一个完整的镜像可以支撑多个容器的运行&#xff0c;在Docker的整个使用过程中&#xff0c;进入一个已经定型的容器之后&#xff0c;就可以在容器中进行操作&#xff0c;最常见的…

JVM面试题--JVM组成

JVM是什么 Java Virtual Machine Java程序的运行环境&#xff08;java二进制字节码的运行环境&#xff09; 运行流程 什么是程序计数器&#xff1f; 程序计数器&#xff1a;线程私有的&#xff0c;内部保存的字节码的行号。用于记录正在执行的字节码指令的地址。 我们知道ja…

16.M端事件和JS插件

16.1移动端 移动端也有自己独特的地方 ●触屏事件touch (也称触摸事件)&#xff0c;Android 和I0S都有。 ●touch对象代表一个触摸点。触摸点可能是一根手指&#xff0c;也可能是一根触摸笔。触屏事件可响应用户手指(或触控笔)对屏幕或者触控板操作。 ●常见的触屏事件如下: …

读书笔记-《ON JAVA 中文版》-摘要21第十九章 类型信息-2]

文章目录 第十九章 类型信息7. 动态代理8. Optional类9. 接口和类型10. 本章小结 第十九章 类型信息 7. 动态代理 代理是基本的设计模式之一。一个对象封装真实对象&#xff0c;代替其提供其他或不同的操作—这些操作通常涉及到与“真实”对象的通信&#xff0c;因此代理通常…

IOS看书最终选择|源阅读转换|开源阅读|IOS自签

环境&#xff1a;IOS想使用 换源阅读 问题&#xff1a;换新手机&#xff0c;源阅读下架后&#xff0c;没有好的APP阅读小说 解决办法&#xff1a;自签APP 转换源仓库书源 最终预览 &#xff1a;https://rc.real9.cn/ 背景&#xff1a;自从我换了新iPhone手机&#xff0c;就无法…

单价20块蓝牙耳机卖爆越南市场,现象级爆款出现?

以儒道为文化底蕴的越南&#xff0c;是与中国最为相近的东南亚国家&#xff0c;"快速增长的劳动人口相对年轻的社会群体"是很多人对越南这个国家的基本认知。背靠庞大的Z世代用户群体&#xff0c;越南社会年轻化消费需求暴涨&#xff0c;手机与数码品类商品作为“年轻…

Maven依赖爆红的几种解决思路

说明&#xff1a;本文介绍Maven依赖爆红&#xff0c;排查错误的几种思路&#xff1b; 思路一&#xff1a;删除本地仓库.lastupdate文件&#xff1b; 找到本地maven仓库&#xff0c;全局搜索.lastupdate文件&#xff0c;把搜索出来的文件全部删除。.lastupdate后缀名的文件&am…

将word每页页眉单独设置

在进行论文排版的时候&#xff0c;总是会出现页眉的页码设置问题&#xff0c;比如出现奇数或偶数页码一致&#xff0c;尝试将前面页码改掉&#xff0c;后面再修改前面也进行了变动&#xff0c;将每页页眉单独设置&#xff1a; &#xff08;1&#xff09;在第一页的最后一行输入…

完全背包(从二维到一维)

图片来源活动 - AcWing 有 N件物品和一个容量为 V 的背包&#xff0c;每件物品有各自的价值且能被选择无数次&#xff0c;要求在有限的背包容量下&#xff0c;装入的物品总价值最大。 一&#xff0c;暴力解法&#xff08;容易超时&#xff09; #include<iostream> usi…

37.利用linprog解 有约束条件多元变量函数最小值(matlab程序)

1.简述 linprog函数主要用来求线型规划中的最小值问题&#xff08;最大值的镜像问题&#xff0c;求最大值只需要加个“-”&#xff09; 2. 算法结构及使用方法 针对约束条件为Axb或Ax≤b的问题 2.1 linprog函数 xlinprog(f,A,b) xlinprog(f,A,b,Aeq,beq) xlinprog(f,A,b,Aeq,…

C语言每日一题:13《数据结构》环形链表。

题目链接&#xff1a; 一.环形链表运动基础。 使用快慢指针利用相对移动的思想&#xff1a; 1.第一种情况&#xff1a; 1,令快指针&#xff08;fast&#xff09;速度为2. 2.慢指针&#xff08;slow&#xff09;速度为1. 3.以慢指针进入环中开始。 4。假设slow刚刚进入环中fast…

Linux C++ 链接数据库并对数据库进行一些简单的操作

一.引言&#xff08;写在之前&#xff09; 在我们进行网络业务代码书写的时候&#xff0c;我们总是避免对产生的数据进行增删改查&#xff0c;为此&#xff0c;本小博主在这里简历分享一下自己在Linux中C语言与数据之间交互的代码的入门介绍。 二.代码书写以及一些变量和函数的…