草庐IT

面试—C++《智能指针》常考点

终为nullptr 2023-04-10 原文

目录

1.为什么需要智能指针

2. 内存泄漏

2.1 什么是内存泄漏,内存泄漏的危害

2.2 内存泄漏分类

2.3 如何检测内存泄漏

2.4如何避免内存泄漏

3.智能指针的使用及原理

3.3 std::auto_ptr

3.4 std::unique_ptr

3.5 std::shared_ptr 


1.为什么需要智能指针

下面我们先分析一下下面这段程序有没有什么内存方面的问题?

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	// 1、如果p1这里new 抛异常会如何?
	// 2、如果p2这里new 抛异常会如何?
	// 3、如果div调用这里又会抛异常会如何?
	int* p1 = new int;
	int* p2 = new int;
	cout << div() << endl;
	delete p1;
	delete p2;
}
int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

上面的三种情况会有内存泄漏的问题

2. 内存泄漏

2.1 什么是内存泄漏,内存泄漏的危害

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内
存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对
该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现
内存泄漏会导致响应越来越慢,最终卡死。

void MemoryLeaks()
{
 // 1.内存申请了忘记释放
 int* p1 = (int*)malloc(sizeof(int));
 int* p2 = new int;
 // 2.异常安全问题
 int* p3 = new int[10];
 Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
 delete[] p3;
}

2.2 内存泄漏分类

C/C++程序中一般我们关心两种方面的内存泄漏:
堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一
块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分
内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放
掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

2.3 如何检测内存泄漏

在linux下内存泄漏检测:linux下几款内存泄漏检测工具
在windows下使用第三方工具:VLD工具说明
其他工具:内存泄漏工具比较

2.4如何避免内存泄漏

1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:
这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智
能指针来管理才有保证。
2. 采用RAII思想或者智能指针来管理资源。
3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
4. 出问题了使用内存泄漏工具检测。
总结一下:
内存泄漏非常常见,解决方案分为两种:

1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具。

3.智能指针的使用及原理

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在
对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做
法有两大好处:
不需要显式地释放资源。
采用这种方式,对象所需的资源在其生命期内始终保持有效

// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr 
{
public:
	//RAII
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~SmartPtr()
	{
		delete[] _ptr;
		cout << "delete[]:" << _ptr << endl;
	}
	//像指针一样:
	T* operator->()
	{
		return _ptr;
	}
	T& operator*()
	{
		return *_ptr;
	}
private:
	T* _ptr;
};
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	SmartPtr<int> sp1(new int[10]);
	SmartPtr<int> sp2(new int[10]);
	cout << div() << endl;
}
int main()
{
	try 
	{
		Func();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

运行截图:

3.3 std::auto_ptr

std::auto_ptr文档

C++98版本的库中就提供了auto_ptr的智能指针。下面演示的auto_ptr的使用及问题。
auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现了一份ns::auto_ptr来了解它的原理

// C++98 管理权转移 auto_ptr
namespace ns
{
	template<class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}
		auto_ptr(auto_ptr<T>& sp)
			:_ptr(sp._ptr)
		{
			// 管理权转移
			sp._ptr = nullptr;
		}
		auto_ptr<T>& operator=(auto_ptr<T>& ap)
		{
			// 检测是否为自己给自己赋值
			if (this != &ap)
			{
				// 释放当前对象中资源
				if (_ptr)
					delete _ptr;
				// 转移ap中资源到当前对象中
				_ptr = ap._ptr;
				ap._ptr = NULL;
			}
			return *this;
		}
		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}
		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};
}
 //结论:auto_ptr是一个失败设计,很多公司明确要求不能使用auto_ptr
int main()
{
	ns::auto_ptr<int> sp1(new int);
	ns::auto_ptr<int> sp2(sp1); // 管理权转移
	// sp1悬空
	*sp2 = 10;
	cout << *sp2 << endl;
	cout << *sp1 << endl;
	return 0;
}

运行截图:

3.4 std::unique_ptr

C++11中开始提供更靠谱的unique_ptr

unique_ptr官方文档

unique_ptr的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份unique_ptr来了解它的原

namespace ns
{
	template<class T>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}
		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}
		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		unique_ptr(const unique_ptr<T>& sp) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
	private:
		T* _ptr;
	};
}
int main()
{
	 ns::unique_ptr<int> sp1(new int);
	 ns::unique_ptr<int> sp2(sp1);

	 std::unique_ptr<int> sp1(new int);
	 std::unique_ptr<int> sp2(sp1);

 return 0;
}

运行截图:

3.5 std::shared_ptr 

C++11中开始提供更靠谱的并且支持拷贝的shared_ptr

std::shared_ptr文档

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

namespace ns
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pRefCount(new int(1))
		{}
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pRefCount(sp._pRefCount)
		{
			++(*_pRefCount);
		}
		void Release()
		{
			if (--(*_pRefCount) == 0)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
				delete _pRefCount;
			}
		}
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//if (this != &sp)
			if (_ptr != sp._ptr)
			{
				Release();
				_ptr = sp._ptr;
				_pRefCount = sp._pRefCount;
				++(*_pRefCount);
			}
			return *this;
		}
		~shared_ptr()
		{
			Release();
		}
		// 像指针一样使用
        int use_count()
		{
			return *_pRefCount;
		}
		T* get() const
		{
			return _ptr;
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pRefCount;
	};
}
int main()
{
	ns::shared_ptr<int> sp1(new int(10));
	ns::shared_ptr<int> sp2(sp1);
	ns::shared_ptr<int> sp3(sp2);

	ns::shared_ptr<int> sp4(new int(10));
	ns::shared_ptr<int> sp5(sp4);
	sp1 = sp5;
	return 0;
}

 运行截图:

存在的问题:当多个线程同时进行拷贝的时候就会出现数据不一致的问题

int main()
{
	int n = 1000;
	ns::shared_ptr<int>sp1(new int(10));
	thread t1([&]()
		{
			for (int i = 0; i < n; i++)
			{
				ns::shared_ptr<int> sp2(sp1);
			}
		});
	thread t2([&]()
		{
			for (int i = 0; i < n; i++)
			{
				ns::shared_ptr<int> sp3(sp1);
			}
		});
	t1.join();
	t2.join();
	cout << sp1.use_count() << endl;
	cout << sp1.get() << endl;

	return 0;
}

运行截图:

 解决方式:在对象拷贝的过程中进行加锁

namespace ns
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pRefCount(new int(1))
			,_pmut(new mutex)
		{}
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pRefCount(sp._pRefCount)
			, _pmut(sp._pmut)
		{
			_pmut->lock();
			++(*_pRefCount);
			_pmut->unlock();

		}
		void Release()
		{
			//释放锁
			bool flag = false;
			_pmut->lock();
			if (--(*_pRefCount) == 0)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
				delete _pRefCount;
				flag = true;
			}
			_pmut->unlock();
			if (flag)
				delete _pmut;
		}
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				Release();
				_ptr = sp._ptr;
				_pRefCount = sp._pRefCount;
				_pmut->lock();
				++(*_pRefCount);
				_pmut->unlock();

			}
			return *this;
		}
		int use_count()
		{
			return *_pRefCount;
		}
		T* get() const
		{
			return _ptr;
		}
		~shared_ptr()
		{
			Release();
		}
		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pRefCount;
		mutex* _pmut;
	};
}

运行截图:

此时解决了拷贝时引用计数数据不一致的问题,但是在访问资源的时候又存在线程安全的问题:

struct Date
{
	int _year = 0;
	int _month = 0;
	int _day = 0;
};
int main()
{
	int n = 10000;
	ns::shared_ptr<Date>sp1(new Date());
	thread t1([&]()
		{
			for (int i = 0; i < n; i++)
			{
				ns::shared_ptr<Date> sp2(sp1);
				sp2->_year++;
				sp2->_month++;
				sp2->_day++;
			}
		});
	thread t2([&]()
		{
			for (int i = 0; i < n; i++)
			{
				ns::shared_ptr<Date> sp3(sp1);
				sp3->_year++;
				sp3->_month++;
				sp3->_day++;
			}
		});
	t1.join();
	t2.join();
	cout << sp1.use_count() << endl;
	cout << sp1.get() << endl;
	cout << sp1->_year << endl;
	cout << sp1->_month << endl;
	cout << sp1->_day << endl;
	return 0;
}

运行截图:

解决方案:在外部访问资源的时候给每个线程进行加锁:

int main()
{
	int n = 10000;
	mutex mut;
	ns::shared_ptr<Date>sp1(new Date());
	thread t1([&]()
		{
			for (int i = 0; i < n; i++)
			{
				mut.lock();
				ns::shared_ptr<Date> sp2(sp1);
				sp2->_year++;
				sp2->_month++;
				sp2->_day++;
				mut.unlock();
			}
		});
	thread t2([&]()
		{
			for (int i = 0; i < n; i++)
			{
				mut.lock();
				ns::shared_ptr<Date> sp3(sp1);
				sp3->_year++;
				sp3->_month++;
				sp3->_day++;
				mut.unlock();
			}
		});
	t1.join();
	t2.join();
	cout << sp1.use_count() << endl;
	cout << sp1.get() << endl;
	cout << sp1->_year << endl;
	cout << sp1->_month << endl;
	cout << sp1->_day << endl;
	return 0;
}

运行截图:

总结:

shared_ptr 本身是线程安全的。(拷贝和析构时,引用计数++ --是线程安全的)

shared_ptr 管理资源的访问不是线程安全的,需要用的地方自行保护。

std::shared_ptr的循环引用

struct ListNode
{
	int _data;
	ns::shared_ptr<ListNode> _prev;
	ns::shared_ptr<ListNode> _next;
	~ListNode() { cout << "~ListNode()" << endl; }
};
int main()
{
	ns::shared_ptr<ListNode> node1(new ListNode);
	ns::shared_ptr<ListNode> node2(new ListNode);
	//不存在循环引用:
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	//循环引用
	node1->_next = node2;
	node2->_prev = node1;
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	return 0;
}

运行截图:

循环引用分析:
1. node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动
delete。
2. node1的_next指向node2,node2的_prev指向node1,引用计数变成2。
3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上
一个节点。
4. 也就是说_next析构了,node2就释放了。
5. 也就是说_prev析构了,node1就释放了。
6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev
属于node2成员,所以这就叫循环引用,谁也不会释放。 

如图所示:

解决方案:在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了 

简单模拟实现weak_ptr:

namespace ns
{
	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr){}
		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get()) {}
		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.get();
			return *this;
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};
}

测试代码:

struct ListNode
{
	int _data;
    //可以指向/访问资源,但是不参与资源的管理,不增加引用计数
	ns::weak_ptr<ListNode> _prev;
	ns::weak_ptr<ListNode> _next;
	~ListNode() { cout << "~ListNode()" << endl; }
};
int main()
{
	ns::shared_ptr<ListNode> node1(new ListNode);
	ns::shared_ptr<ListNode> node2(new ListNode);
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	node1->_next = node2;
	node2->_prev = node1;
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	return 0;
}

运行截图:

解决原理:

node1->_next = node2;和node2->_prev = node1时weak_ptr的_next和_prev不会增加node1和node2的引用计数。 

定址删除器

如果是new[]出来的对象,通过智能指针如何管理呢?

int main()
{
	ns::shared_ptr<string> sp2(new string[10]);
	return 0;
}

运行截图:

解决方式:在析构的时候给类对象提供专门的删除器进行删除

//定址删除器:
template<class T>
struct Deletearray
{
	void operator()(const T* ptr)
	{
		delete[] ptr;
		cout << "delete[]" << endl;
	}
};
int main()
{
	//可以通过自定义的仿函数进行删除:
	std::shared_ptr<string> sp2(new string[10],Deletearray<string>());
	//也可以通过lambda表达式进行删除:
	std::shared_ptr<string> sp3(new string[10], [](string* ptr) {delete[] ptr; });
	return 0;
}

 运行截图:

模拟实现库中的shared_ptr:

namespace ns
{
	template<class T>
	class default_delete
	{
	public:
		void operator()(const T* ptr)
		{
			delete ptr;
		}
	};
	template<class T,class D = default_delete<T>>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pRefCount(new int(1))
			, _pmut(new mutex)
		{}
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pRefCount(sp._pRefCount)
			, _pmut(sp._pmut)
		{
			_pmut->lock();
			++(*_pRefCount);
			_pmut->unlock();

		}
		void Release()
		{
			//释放锁
			bool flag = false;
			_pmut->lock();
			if (--(*_pRefCount) == 0)
			{
				//cout << "delete:" << _ptr << endl;
				//delete _ptr;
				//转化为定址删除器进行删除
				_del(_ptr);
				delete _pRefCount;
				flag = true;
			}
			_pmut->unlock();
			if (flag)
				delete _pmut;
		}
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				Release();
				_ptr = sp._ptr;
				_pRefCount = sp._pRefCount;
				_pmut = sp._pmut;
				_pmut->lock();
				++(*_pRefCount);
				_pmut->unlock();

			}
			return *this;
		}
		int use_count() const
		{
			return *_pRefCount;
		}
		T* get() const
		{
			return _ptr;
		}
		~shared_ptr()
		{
			Release();
		}
		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pRefCount;
		mutex* _pmut;
		D _del;
	};
}
//定址删除器:
template<class T>
struct Deletearray
{
	void operator()(const T* ptr)
	{
		delete[] ptr;
		cout << "delete[]" << endl;
	}
};
int main()
{
	//可以通过自定义的仿函数进行删除:
	ns::shared_ptr<string,Deletearray<string>> sp2(new string[10]);
	return 0;
}

运行截图:

有关面试—C++《智能指针》常考点的更多相关文章

  1. 【Java 面试合集】HashMap中为什么引入红黑树,而不是AVL树呢 - 2

    HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候

  2. ruby 变量作为同一对象(指针?) - 2

    >>a=5=>5>>b=a=>5>>b=4=>4>>a=>5如何将“b”设置为实际的“a”,以便在示例中,变量a也将变为4。谢谢。 最佳答案 classRefdefinitializeval@val=valendattr_accessor:valdefto_s@val.to_sendenda=Ref.new(4)b=aputsa#=>4putsb#=>4a.val=5putsa#=>5putsb#=>5当您执行b=a时,b指向与a相同的对象(它们具有相同的object_id).当你执行a=some_other_thing时,a将指向

  3. 玩以太坊链上项目的必备技能(初识智能合约语言-Solidity之旅一) - 2

    前面一篇关于智能合约翻译文讲到了,是一种计算机程序,既然是程序,那就可以使用程序语言去编写智能合约了。而若想玩区块链上的项目,大部分区块链项目都是开源的,能看得懂智能合约代码,或找出其中的漏洞,那么,学习Solidity这门高级的智能合约语言是有必要的,当然,这都得在公链``````以太坊上,毕竟国内的联盟链有些是不兼容Solidity。Solidity是一种面向对象的高级语言,用于实现智能合约。智能合约是管理以太坊状态下的账户行为的程序。Solidity是运行在以太坊(Ethereum)虚拟机(EVM)上,其语法受到了c++、python、javascript影响。Solidity是静态类型

  4. 西安华为OD面试体验 - 2

    西安华为OD面试体验开始投简历技术面试进展工作进展开始投简历去年一整年一直在考研和工作之间纠结,感觉自己的状态好像当时的疫情一样差劲。之前刚毕业的时候投了个大厂的简历,结果一面写算法的时候太拉跨了,虽然知道时dfs但是代码熟练度不够,放在平时给足时间自己可以调试通过,但是熟练度不够那面试当时就写不出来被刷了。说真的算法学到后期我感觉最重要的是熟练度和背板子(对于我这种普通玩家来说),面试题如果一上来短时间内想不出思路就完蛋了。然后由于当时找的工作不是很理想就又想考研了。但是考研是有风险的,我自我感觉自己可能冲不上那个学校,而找工作一个没成可以继续找嘛。本着抱着试试看的态度在boss上投了简历,

  5. 智能客服 | 浅谈人工智能聊天机器人ChatGPT - 2

    2022年底,OpenAI的预训练模型ChatGPT给人工智能领域的爱好者和研究人员留下了深刻的印象和启发,他展现的惊人能力将人工智能的研究和应用热度推向高潮,网上也充斥着和ChatGPT的各种聊天,他可以作诗、写小说、写代码、讨论疫情问题等。下面就是一些他的神回复:人命关天的坑: 写歌,留给词作者的机会不多了。。。 回答人类怎么样面对人工智能: 什么是ChatGPT?借用网上的一段介绍,ChatGPT是由人工智能研究实验室OpenAI在2022年11月30日发布的全新聊天机器人模型,一款人工智能技术驱动的自然语言处理工具。它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动

  6. [面试直通版]操作系统核心之进程、线程与协程(下) - 2

    点击->操作系统复习的文章集目录操作系统线程线程是什么进程与线程的关系用户态/内核态操作系统资源管理内核态用户态内核态/用户态切换程序运行类型分析计算密集型IO密集型结合进程,线程来理解程序运行类型分析协程基础上下文切换协程协程为什么叫协作式线程?协程的优缺点操作系统线程典型问题:简述进程和线程的区别以下内容带您一步步了解线程是什么比进程更小的独立运行的基本单位-线程(Threads)线程的提出主要是为了提高系统内程序并发执行的程度,从而进一步提升系统的吞吐量,充分发挥多核CPU的优越性而设计的引入进程是为了操作系统更加方便地管理程序,使得多个程序能并发管理和执行而线程则是为了减少程序在并发执

  7. ruby - 对象分配和指针 - 2

    我对Ruby中的对象分配和指针有点困惑,编写了这段代码来测试我的假设。classFooattr_accessor:one,:twodefinitialize(one,two)@one=one@two=twoendendbar=Foo.new(1,2)beans=barputsbarputsbeansbeans.one=2putsbarputsbeansputsbeans.oneputsbar.one我曾假设,当我将bar分配给beans时,它会创建该对象的副本,并且修改一个不会影响另一个。唉,输出显示不是这样。^_^[jergason:~]$rubytest.rb####22我相信这些

  8. ruby - 为什么 Gosu 隐藏我的鼠标指针? - 2

    我正在使用Gosugem进行一些图形编程。问题是,当我创建一个窗口时,我的鼠标指针被隐藏了。我可以猜到鼠标在某个时刻的位置,我可以凭直觉点击,但我的用户可能不会。如何显示指针? 最佳答案 如果你想使用系统光标你可以这样做classWindow查看libgosu的文档RubyGosurdocReference/Window 关于ruby-为什么Gosu隐藏我的鼠标指针?,我们在StackOverflow上找到一个类似的问题: https://stackoverf

  9. 【华为OD技术面试 | 真八股 】MySQL联合索引,谈springIOC的理解,谈springAOP的理解,Erika和zookeeper等问题 - 2

    文章目录华为OD面试流程1.mysql数据库建了两个字段,且设置了联合索引,如果其中有一个字段为空会出现什么问题?2.谈谈springIOC的理解,有什么好处,解决了什么问题3.谈谈springAOP的理解,切面编程有没有实际应用,有哪些注解,作用是什么,有那些应用场景?4.Erika和zookeeper有了解过吗,作用是什么,主要解决了什么问题5.谈谈JDK、JRE、JVM的理解,区别是什么6.谈谈对泛型的理解7.JVM的组成华为OD面试流程机试:三道算法题,关于机试,橡皮擦已经准备好了各语言专栏,可以直接订阅。性格测试:机试技术一面(本专栏核心)技术二面(本专栏核心)主管面试定级定薪发of

  10. 基于python的短视频智能推荐/django的影视网站/视频推荐系统 - 2

    摘要本论文主要论述了如何使用Python技术开发一个短视频智能推荐,本系统将严格按照软件开发流程进行各个阶段的工作,采用B/S架构,面向对象编程思想进行项目开发。在引言中,作者将论述短视频智能推荐的当前背景以及系统开发的目的,后续章节将严格按照软件开发流程,对系统进行各个阶段分析设计。 短视频智能推荐的主要使用者分为管理员和用户,实现功能包括管理员:首页、个人中心、用户管理、热门视频管理、用户上传管理、系统管理,用户:首页、个人中心、用户上传管理、我的收藏管理,前台首页;首页、热门视频、用户上传、公告信息、个人中心、后台管理等功能。由于本网站的功能模块设计比较全面,所以使得整个短视频智能推荐信

随机推荐