【C++】vector的模拟实现【完整版】

news2024/11/25 16:37:34

目录

一、vector的默认成员函数 

1、vector类的大体结构

 2、无参构造函数

3、拷贝构造函数

 4、Swap(operator=需要用)

5、赋值重载operator=

 6、析构函数

二、vector的三种遍历方式

 1、size和capacity(大小和容量)

2、 operator[]遍历

3、迭代器iterator遍历和范围for

三、vector相关的增容和删除

 1、reserve (指定容量)

2、resize(指定大小)

3、push_back(尾插)

4、pop_back(尾删)

5、insert

6、erase

四、完整代码 

vector.h: 

test.cpp:


一、vector的默认成员函数 

1、vector类的大体结构

vector的成员变量本质是由三个T类型(模板)的指针变量组成的,T是因为用了模板,因为vector具体存储什么类型的需要指定

namespace mz
{
using std::cout;
using std::endl;
using std::string;

	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
    private:
	    iterator _start;
	    iterator _finish;
	    iterator _endofstorage;
    }
}

 ①、为什么要在新的命名空间中模拟实现vector?

防止与库里面的vector冲突,因为我们要自己模拟实现一个

②、iterator为什么要用两个版本(const和非const)?

因为迭代器遍历时我们要有只读又能读又能写的状态,所以要有个const版本的

 2、无参构造函数

vector()
	:_start(nullptr)
	,_finish(nullptr)
	,_endofstorage(nullptr)
{}

3、拷贝构造函数

注:此函数最好放最后面看,因为其内部调用了下文才讲的成员函数

①、正常版本

首先我们不自己实现,就是浅拷贝,会出现问题,故需我们自己实现深拷贝,v2(v1),即只要让v2拷贝一份新的数据即可(使v2和v1不是指向同一份数据)

②、现代版本

v2(v1),先让v2 reserve和v1一样的容量大小,防止频繁增容降低效率,再把v1中的每个数据尾插到v2中即可

为什么要const auto&e?

其是一种用于迭代容器元素的引用方式。它表示在循环过程中,我们用一个常量引用来访问容器中的元素。好处是不会对容器进行修改,并不会产生拷贝操作,可提高代码的效率。

在用范围for循环时,如果我们只是想读取容器中的元素而不对其进行修改,可以使用const auto&来声明循环变量。例如,当我们遍历一个vector或者数组时,可以使用const auto&来避免对容器进行修改操作。

例如,当我们遍历一个数组时,如果使用const auto&来声明循环变量,则不能修改数组中的元素。例如,对于以下代码:

int arr = {0, 1, 2, 3, 4};
for (const auto& a : arr) {
a = 1; // 错误,不能修改
cout << a << “\t”;
}

由于使用了const auto&声明循环变量a,所以对a进行修改会导致编译错误。

//v2(v1) 正常版本的实现
/*vector(const vector<T>& v)
{
	_start = new T[v.capacity()];
	_finish = _start;
	_endofstorage = _start + v.capacity();

	for (size_t i = 0; i < v.size(); ++i)
	{
		*_finish = v[i];
		++_finish;
	}
}*/

//v2(v1) 现代版本的实现(更推荐)
vector(const vector<T>& v)
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
{
	reserve(v.capacity());

	for (const auto& e : v)
		push_back(e);
}

 4、Swap(operator=需要用)

为什么需要自己提供swap,而不调用里提供的?

因为库里面提供的对于自定义类型代价极大,因为需要深拷贝,但对于内置类型用库函数里面的还好,所以应该自己写一个成员函数swap

void swap(vector<T>& v)
{
	//调用全局的swap,交换指针,其为内置类型,无需深拷贝
	//代价相比于直接调用库里面函数交换比较小
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_endofstorage, v._endofstorage);
}

5、赋值重载operator=

①、正常版本

先要避免自己给自己赋值,先释放旧空间,再使_start指向一份自己新开辟的新空间,再把需要赋值的数据拷过去即可

②、现代版本(更推荐)

v是v3的临时拷贝,然后v会与v1交换,出了函数v就被销毁了,不用我们自己再销毁了,省力

//v1 = v3 正常版本的实现
/*vector<T>& operator=(const vector<T>& v)
{
	if (this != &v)
	{
		delete[] _start;
		_start = new T[v.capacity()];

		memcpy(_start, v._start, sizeof(T) * v.size());
	}

	return *this;
}*/

//v1 = v3 现代版本
vector<T>& operator=(vector<T> v)
{
	swap(v);//this->swap(v);

	return *this;
}

 6、析构函数

    ~vector()
{
	if (_start)
	{
		delete[]_start;
		_start = _finish = _endofstorage = nullptr;
    }
}

二、vector的三种遍历方式

 1、size和capacity(大小和容量)

原理:指针相减就是指针间的元素个数

	size_t size()const
	{
		return _finish - _start;
	}
	size_t capacity()const
	{
		return _endofstorage - _start;
	}

①、为什么要加const?

成员函数只要不改变成员变量的,最好都要加上const  

2、 operator[]遍历

T& operator[](size_t i)
{
	//确保范围的合法性
	assert(i < size());//记得引头文件assert.h

	return _start[i];
}

const T& operator[](size_t i)const
{
	assert(i < size());

	return _start[i];
}

①、为什么要实现两个版本的operator[]?

直接把非const的operator[]加上const不行吗?不可以,因为operator[]访问完后,有两种状态:1、只读 2、可读可写  你直接加上const就只读了,万一我想改数据又不行了,故写两个版本的(const和非const版本) 

3、迭代器iterator遍历和范围for

迭代器有只读的和可读可写的,故也要写两个版本

iterator begin()
{
	return _start;
}

iterator end()
{
	return _finish;
}

const_iterator begin()const
{
	return _start;
}

const_iterator end()const
{
	return _finish;
}

只要把迭代器写出来了,迭代器遍历和范围for遍历就都可以用了

测试operator[]迭代器范围for三种遍历

void test1()
{
	vector<int>v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	cout << v.size() << endl;
	cout << v.capacity() << endl;
	
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		*it += 1;
		cout << *it << " ";
		++it;
	}
	cout << endl; 

	for (auto& e : v)
	{
		e -= 1;
		cout << e << " ";
	}
	cout << endl;

	for (size_t i = 0; i < v.size(); ++i)
	{
		cout << v[i] << " ";
	}
	cout << endl;

运行结果:

三、vector相关的增容和删除

 1、reserve (指定容量)

void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t sz = size();//大小要在增容前就算好
		T* tmp = new T[n];
		if (_start)
		{
			//memcpy(tmp, _start, sizeof(T) * sz);//按字节拷贝,浅拷贝
			//作深拷贝,使新旧空间指向自己对应的数据
			for (size_t i = 0; i < sz; ++i)
			{
				tmp[i] = _start[i];//调用的是T的operator=,深拷贝
			}
			delete[]_start;
		}
		_start = tmp;
		_finish = tmp + sz;//不能写为=tmp + size()
		_endofstorage = tmp + n;
	}
}

①、为什么要在增容前就算出大小?

因为增容涉及新和旧空间的问题,比如_finish = tmp + size(),而size的求法是用_finish - _start,但_start已指向新空间,可_finish还是指向旧空间,两块不连续的空间相减就是随机值了,故要在增容之前就把大小算出来,不然增容之后的大小就是随机值了

②、为什么不能用memcpy拷贝数据?

测试时,当用string作为vector存储的类型时,程序崩溃了,为什么?

本质原因是因为增容中用了memcpy,memcpy导致了问题的出现,因为memcpy是按字节拷贝的,它本质上造成了浅拷贝问题, memcpy使新旧空间都指向了一份数据,旧空间释放后,它指向的对应数据也被释放,而新空间指针还是指向这份旧空间,这就造成非法访问,所以新空间与旧空间不应指向同一份数据,应该不用memcpy,写成深拷贝

解决(reserve的实现不用memcpy): 

把旧空间的每个值赋值给新空间对应的每个值就好了,就能实现深拷贝了,指向不同的数据

2、resize(指定大小)

void resize(size_t n, const T& val = T())//因为不知道T的类型,故给T类型的缺省值
{
	if (n < size())
	{
		_finish = _start + n;
	}
	else
	{
		if (n > capacity())
		{
			reserve(n);
		}
		//填数据不能用memset

		//填数据
		while (_finish < _start + n)
		{
			*_finish = val;
			++_finish;
		}
	}
}

①、填数据为什么不能用memset?

当对于a数组,设置为0时,没问题,但各设置为1和2时,就出现随机值了 ,因为memset只适合把所有值初始化为0,当初始化为1时,是把每个字节初始化为1 

即00000001 00000001 00000001 00000001,这就不是整形的1了(2也同理,就初始化0可以)!memset连内置类型都处理不好,别说自定义类型了,问题会更大,本质上就是因为memxxx类函数都是按字节处理的,

即慎用memxxx,因为其按字节进行操作

②、const T& val = T()中的T()使什么意思?

因为不知道T的类型,故用T的缺省值

C++中内置类型也可以像自定义类型那样调用构造函数,严格来说,内置类型是没有构造函数的,但C++强行赋予了这个构造函数概念,是为了更好地支持模板


    int i = int();//0
    int j = int(1);//1
    double d = double();//0.0000000000
    double e = double(1.1);//1.100000000

3、push_back(尾插)

void push_back(const T& x) 
{
	//方法一
	if (_finish == _endofstorage)
	{
		size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
		reserve(newcapacity);
	}
	*_finish = x;
	++_finish;

	//方法二、调用insert
	//insert(_finish, x);
}

4、pop_back(尾删)

void pop_back()
{
	//方法一
	assert(_start < _finish);
	--_finish;
	//方法二、复用erase
	erase(_finish - 1);
}

5、insert

void insert(iterator pos, const T& x)
{
	assert(pos <= _finish);//pos == _finish时,相当于尾插

	//满了需要扩容
	if (_finish == _endofstorage)
	{
		size_t n = pos - _start;//算出原pos距离
		size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
		reserve(newcapacity);
		//增完容后要更新pos的位置,因为增容后原来的pos就失效了
		pos = _start + n;//新的_start + n
	}
	//插入之前需要往后挪动1个数据
	iterator end = _finish;
	while (end > pos)
	{
		*end = *(end - 1);
		--end;
	}
	//插入数据
	*pos = x;
	++_finish;
}

为什么需要提前算出n?

有迭代器失效的问题,因为pos是迭代器类型,扩容前指向旧空间的某一位置,而reserve调用后会扩容,而我们是扩容完才插入数据的,此时pos无效,因为旧空间已经释放了,它这个迭代器还指向那里就失效了,故我们要更新pos位置,使它指向新空间,所以要先算n,即原pos的位置

6、erase

iterator erase(iterator pos)
{
	assert(pos < _finish);

	iterator it = pos + 1;
	while (it < _finish)
	{
		*(it - 1) = *it;
		++it;
	}
	--_finish;

	return pos;//返回被删除数据的下一位置,那就还是原位置,因为那个数据被删除了
}

 注意:erase是返回被删除数据的下一位置,当要被删除的数据被删除了,erase原地不动的话,就已自动指向了下一位置,因为那个数据被删除了

最后一个问题

如果T是字符串的话代码逻辑会不会忘掉'\0'?

不会,vector<char>末尾没有'\0',string才有,这也是他们的差别

四、完整代码 

总共分为两个文件:vector.h和test.cpp

vector.h: 

#pragma once
#include<assert.h>
#include<string>

namespace mz
{
using std::cout;
using std::endl;
using std::string;

	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		vector()
			:_start(nullptr)
			,_finish(nullptr)
			,_endofstorage(nullptr)
		{}

		//v2(v1) 正常版本的实现
		/*vector(const vector<T>& v)
		{
			_start = new T[v.capacity()];
			_finish = _start;
			_endofstorage = _start + v.capacity();

			for (size_t i = 0; i < v.size(); ++i)
			{
				*_finish = v[i];
				++_finish;
			}
		}*/

		//v2(v1) 现代版本的实现(更推荐)
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			reserve(v.capacity());

			for (const auto& e : v)
				push_back(e);
		}

		//v1 = v3 正常版本的实现
		/*vector<T>& operator=(const vector<T>& v)
		{
			if (this != &v)
			{
				delete[] _start;
				_start = new T[v.capacity()];

				memcpy(_start, v._start, sizeof(T) * v.size());
			}

			return *this;
		}*/

		//v1 = v3 现代版本
		vector<T>& operator=(vector<T> v)
		{
			swap(v);//this->swap(v);

			return *this;
		}

		void swap(vector<T>& v)
		{
			//调用全局的swap,交换指针,其为内置类型,无需深拷贝
			//代价相比于直接调用库里面函数交换比较小
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage, v._endofstorage);
		}
		

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin()const
		{
			return _start;
		}

		const_iterator end()const
		{
			return _finish;
		}

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();//大小要在增容前就算好
				T* tmp = new T[n];
				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T) * sz);//按字节拷贝,浅拷贝
					//作深拷贝,使新旧空间指向自己对应的数据
					for (size_t i = 0; i < sz; ++i)
					{
						tmp[i] = _start[i];//调用的是T的operator=,深拷贝
					}
					delete[]_start;
				}
				_start = tmp;
				_finish = tmp + sz;//不能写为=tmp + size()
				_endofstorage = tmp + n;
			}
		}

		void resize(size_t n, const T& val = T())//因为不知道T的类型,故给T类型的缺省值
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				if (n > capacity())
				{
					reserve(n);
				}
				//填数据不能用memset

				//填数据
				while (_finish < _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
		}

		void push_back(const T& x)
		{
			//方法一
			if (_finish == _endofstorage)
			{
				size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
				reserve(newcapacity);
			}
			*_finish = x;
			++_finish;

			//方法二、调用insert
			//insert(_finish, x);
		}

		void pop_back()
		{
			//方法一
			assert(_start < _finish);
			--_finish;
			//方法二、复用erase
			erase(_finish - 1);
		}

		void insert(iterator pos, const T& x)
		{
			assert(pos <= _finish);//pos == _finish时,相当于尾插

			//满了需要扩容
			if (_finish == _endofstorage)
			{
				size_t n = pos - _start;//算出原pos距离
				size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
				reserve(newcapacity);
				//增完容后要更新pos的位置,因为增容后原来的pos就失效了
				pos = _start + n;//新的_start + n
			}
			//插入之前需要往后挪动1个数据
			iterator end = _finish;
			while (end > pos)
			{
				*end = *(end - 1);
				--end;
			}
			//插入数据
			*pos = x;
			++_finish;
		}

		iterator erase(iterator pos)
		{
			assert(pos < _finish);

			iterator it = pos + 1;
			while (it < _finish)
			{
				*(it - 1) = *it;
				++it;
			}
			--_finish;

			return pos;//返回被删除数据的下一位置,那就还是原位置,因为那个数据被删除了
		}

		T& operator[](size_t i)
		{
			//确保范围的合法性
			assert(i < size());//记得引头文件assert.h

			return _start[i];
		}

		const T& operator[](size_t i)const
		{
			assert(i < size());

			return _start[i];
		}

		size_t size()const
		{
			return _finish - _start;
		}

		size_t capacity()const
		{
			return _endofstorage - _start;
		}
		
		~vector()
		{
			if (_start)
			{
				delete[]_start;
				_start = _finish = _endofstorage = nullptr;
			}
		}

	private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;
	};


	void print_vector(const vector<int>& v)
	{
		vector<int>::const_iterator it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

	void test1()
	{
		vector<int>v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);

		cout << v.size() << endl;
		cout << v.capacity() << endl;
		
		vector<int>::iterator it = v.begin();
		while (it != v.end())
		{
			*it += 1;
			cout << *it << " ";
			++it;
		}
		cout << endl; 

		for (auto& e : v)
		{
			e -= 1;
			cout << e << " ";
		}
		cout << endl;

		for (size_t i = 0; i < v.size(); ++i)
		{
			cout << v[i] << " ";
		}
		cout << endl;
	}

	void test2()
	{
		vector<int>v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);
		v.push_back(6);

		v.insert(v.begin(), 0);//头插0
		print_vector(v);

		//删除偶数
		vector<int>::iterator it = v.begin();
		while (it != v.end())
		{
			if (*it % 2 == 0)
			{
				it = v.erase(it);
			}
			else
			{
				++it;
			}
		}
		print_vector(v);
	}

	void test3()
	{
		vector<int>v;
		v.reserve(10);
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);
		v.push_back(6);

		v.resize(4);
		print_vector(v);
		cout << v.size() << endl;
		cout << v.capacity() << endl;

		v.resize(8);
		print_vector(v);
		cout << v.size() << endl;
		cout << v.capacity() << endl;

		v.resize(12,int());
		print_vector(v);
		cout << v.size() << endl;
		cout << v.capacity() << endl;

		int i = int();//0
		int j = int(1);//1
		double d = double();//0.0000000000
		double e = double(1.1);//1.100000000
	}

	void test4()
	{
		vector<int>v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);

		vector<int>v2(v1);

		for (size_t i = 0; i < v1.size(); ++i)
		{
			cout << v1[i] << " ";
		}
		cout << endl;

		for (size_t i = 0; i < v2.size(); ++i)
		{
			cout << v2[i] << " ";
		}
		cout << endl;
	
		vector<int>v3;
		v3.push_back(10);
		v3.push_back(20);
		v3.push_back(30);
		v3.push_back(40);

		v1 = v3;
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;
	}

	void test5()
	{
		vector<string>v;
		v.push_back("111111111111111111111");
		v.push_back("222222222222222222222");
		v.push_back("333333333333333333333");
		v.push_back("444444444444444444444");

		
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
	}
}

test.cpp:

#include<iostream>
#include"vector.h"

int main()
{
	mz::test1();
	//memxxx 按字节处理
	/*int a[10];
	memset(a, 0, sizeof(int) * 10);
	memset(a, 1, sizeof(int) * 10);
	memset(a, 2, sizeof(int) * 10);*/

	return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ 判断

C 判断 判断结构要求程序员指定一个或多个要评估或测试的条件&#xff0c;以及条件为真时要执行的语句&#xff08;必需的&#xff09;和条件为假时要执行的语句&#xff08;可选的&#xff09;。 下面是大多数编程语言中典型的判断结构的一般形式&#xff1a; 判断语句 C 编…

基于Dubbo实现服务的远程调用

目录 前言 为什么使用Dubbo Dubbo技术框架 ​编辑 调用关系流程 基础实现 A.提供统一业务Api B.编辑服务提供者Product B.a 添加依赖 B.b 添加Dubbo 配置(基于yaml配置文件) B.c 编写并暴露服务 C.编辑服务消费者 C.a 添加依赖 C.b 添加Dubbo配置 C.c 引用服务 前言…

Arduino程序设计(八)LCD显示实验

LCD显示实验 前言一、基于LM35的LCD显示实验1. LM35简介2. LCD1602简介3. LM35采集环境温度LCD显示 二、基于DS18B20的LCD显示实验1. DS18B20简介2. DS18B20采集环境温度LCD显示 三、基于DHT11采集环境温湿度LCD显示1. DHT11简介2. DHT11采集环境温湿度LCD显示 前言 本文主要介…

高忆管理:a股b股h股n股的区别?

A股、B股、H股、N股&#xff0c;这些股票标识的差异&#xff0c;是初学者往往比较无措的。本文将从多个角度剖析这些股票的区别&#xff0c;协助读者更好地了解和运用出资。 1.不同的出资主体 首要&#xff0c;要了解这些标识的意义。A股、B股、H股、N股是不同的出资主体。A股…

蠕虫病毒流量分析案例

背景 某供排水集团的网络管理员对其网络的健康状况持认可态度&#xff0c;表示网络运行正常&#xff0c;没有发现异常行为。然而&#xff0c;由于网络环境变得越来越复杂&#xff0c;仅凭借传统的网络经验已经不能全面了解网络情况。因此&#xff0c;我们为供排水集团安装了Ne…

cadence后仿真/寄生参数提取/解决pin口提取不全的问题

post-simulation设置顺序与规则 1.Rules 设置 2.inputs设置 3.outputs设置 4.PEX 设置 会出现错误1&#xff0c;后有解决方案 第一步 :Netlist 第二步&#xff1a;LVS 5.RUN PEX 先RUN&#xff0c;后按照图中1 2 3步骤操作 点击OK之后&#xff0c;显示Calibre信息&#xff…

配置 Windows 系统环境变量

直接按键盘上面的 WINS 打开 Windows 搜索 搜索“编辑系统环境变量” 也可以右键此电脑->属性->高级系统设置打开相同的界面 点击环境变量 一般添加就是添加在框出的 Path 里面&#xff0c;双击可以看到现有的环境变量并进行编辑 例如我在博客中写把 Java 的 jdk 解压好…

功能测试—边界值分析法

一、边界值分析法的概念 边界值分析法就是对输入的边界值进行测试的一种黑盒测试方法。通常边界值分析法是作为对等价类划分法的补充&#xff0c;这种情况下&#xff0c;其测试用例来自等价类的边界 1 为什么引入边界值分析法&#xff1f; 测试实践表明&#xff0c;大量的故…

【Springcloud】elk分布式日志

【Springcloud】elk分布式日志 【一】基本介绍【二】Elasticsearch【1】简介【2】下载【3】安装【4】启动 【三】Logstash【1】简介【2】下载【3】安装【4】启动 【四】Kibana【1】简介【2】下载【3】安装【4】启动 【五】切换中文【六】日志收集 【一】基本介绍 &#xff08;…

Codeforces Round 895 (Div. 3)题解

前言&#xff1a; 暂时更新到D&#xff0c;后面的找时间再看 A. Two Vessels 题目A&#xff1a;通过最少操作使得a,b水相等&#xff0c;分abs(a-b)是不是2*c的倍数讨论&#xff0c;2c是因为&#xff0c;a-c bc 总差值为2*c; #include<bits/stdc.h> using namespace s…

CMake高级用法实例分析(学习paddle官方的CMakeLists)

cmake基础学习教程 https://juejin.cn/post/6844903557183832078 官方完整CMakeLists cmake_minimum_required(VERSION 3.0) project(PaddleObjectDetector CXX C)option(WITH_MKL "Compile demo with MKL/OpenBlas support,defaultuseMKL." ON) o…

[论文笔记] Gunrock: A High-Performance Graph Processing Library on the GPU

Gunrock: A High-Performance Graph Processing Library on the GPU Gunrock: GPU 上的高性能图处理库 [Paper] [Code] PPoPP’16 摘要 Gunrock, 针对 GPU 的高层次批量同步图处理系统. 采用了一种新方法抽象 GPU 图分析: 实现了以数据为中心(data-centric)的抽象, 以在结点…

网络编程day070904

目录 将dict.txt导入到数据库中单词一列&#xff0c;意思一列 代码 结果 思维导图 将dict.txt导入到数据库中单词一列&#xff0c;意思一列 代码 #include<myhead.h> #include<sqlite3.h>int do_create(sqlite3 *db); int do_insert(sqlite3 *db, char * Engl…

ILS解析漏洞复现

搭建好ILS后&#xff0c;访问127.0.0.1:8000 写一个phpinfo的脚本 可以看到。现在是不能访问的 赋予 IIS 解析 phpinfo 能力 打开服务器管理器&#xff0c;打开 IIS 管理器 点击处理程序映射 再次访问&#xff0c;发现程序可以访问 将index.php改为index.png 此时php脚本自然是…

Selenium 三种等待方式详解 (强制等待、隐式等待、显示等待)

前言 ①在进行WEB自动化工作时&#xff0c;一般要等待某一页面元素加载完成后&#xff0c;才能对该元素执行操作&#xff0c;否则自动化脚本会抛出找不到元素的错误&#xff0c;这样就要求我们在UI自动化测试的有些场景上加上等待时间。 ②等待方式的设置是保证自动化脚本稳定…

静态代理IP是什么?一文看懂静态代理IP

对于跨境玩家来说&#xff0c;IP代理已经是我们的老朋友了&#xff0c;那么什么是静态IP&#xff1f;有什么用&#xff1f;如何使用&#xff1f;看完这一篇你就懂了。 一、什么是静态代理IP 静态代理IP是指一个固定不变的&#xff0c;不会在网络重新连接时重新建立或者更改的代…

基于VS平台编译带Cuda的OpenCV(内含版本以及整套方案含泪总结)

感谢 先感谢以下帖子&#xff0c;确实很有参考意义&#xff0c;但很多坑还是没总结到&#xff08;我抓狂了&#xff09; 从安装到编译保姆级帖子&#xff1a;https://blog.csdn.net/fengxinzioo/article/details/109402921 大神版核心步骤帖子(主要参考)&#xff1a;https://bl…

促进合作交流|旅游专业老师在加拿大访学期间取得初步成果

N老师拟申报CSC&#xff0c;指定国家且要求接收学校不收取板凳费&#xff0c;最终我们分别获得了澳大利亚科廷大学和加拿大圭尔夫大学的邀请函&#xff08;均未收取板凳费&#xff09;。N老师用前者申报了CSC并获批&#xff0c;因签证原因又用后者申请了改派&#xff0c;并在加…

Java之Collection集合的详细解析

1.Collection集合 1.1数组和集合的区别【理解】 相同点 都是容器,可以存储多个数据 不同点 数组的长度是不可变的,集合的长度是可变的 数组可以存基本数据类型和引用数据类型 集合只能存引用数据类型,如果要存基本数据类型,需要存对应的包装类 1.2集合类体系结构【理解】 …

APUS与腾讯达成战略合作,携手深化产业赋能

9月7—8日&#xff0c;2023腾讯全球数字生态大会在深圳盛大举行&#xff0c;AI大模型企业APUS出席大会&#xff0c;APUS副总裁邓小波与腾讯现场签约战略合作&#xff0c;双方将集中各自优势&#xff0c;在ICT基础设施、大模型生态建设等领域开展广泛、深入合作。此外&#xff0…