list类——常用函数模拟

news2025/1/18 7:43:04

        本篇将对 list 类的常用函数进行模拟。其中主要要点为函数的模拟,另外还会对函数的功能和返回值进行讲解。但 list 可以说是 string vector stack queue …… STL 库中最难实现一个类,因为 list 的迭代器不是很好实现,所以本篇一个很重要的一个点便是 list 的迭代器。不仅仅实现了 list 的迭代器,还实现了 list 中常用的函数。

        list 类的底层为双向循环链表,实现的时候对于双向循环链表介绍的也不是很详细,若想详细的了解双向循环链表,可阅读这篇文章:链表/双向循环链表(C/C++)-CSDN博客

        目录如下:

目录

1. list 的架构

2. list 的实现

2.1 pop and push

2.2 swap、empty、and size

2.3 iterator、insert、erase

2.4 const_iterator

2.5 模板 iterator

2.6 默认成员函数

3. all code

1. list 的架构

        list 类中主要结构为循环双向链表,所以结点为指针包括 next 和 prev。

        list 类主要实现的为一个链表类,所以 list 中的元素即为结点,所以另外我们还需要一个结构体用来充当 list 中的结点,然后 list 中的元素就是维护结点的指针。不仅仅需要一个结构体充当结点,倘若我们只使用指针来表征迭代器,那么想要通过迭代器来访问结点中存储的内容的时候,并不能像使用 *iterator 来访问结点内容,所以我们也还需要一个类来表征迭代器,然后对于迭代器的实现主要还是使用一个结点指针来实现,但是不同的是,我们会在迭代器类中实现运算符重载,以达到像使用 *iterator 来访问结点内容。

        主要的框架如下:

namespace MyList {
	template <class T>
	struct ListNode {
		ListNode* _next;
		ListNode* _prev;
		T _data;
		// 结点的构造函数
		ListNode(const T& data)
			:_next(nullptr)
			, _prev(nullptr)
			, _data(data)
		{}
	};

	// 迭代器类
	template <class T>
	struct ListIterator {
	public:
		typedef ListNode<T> node;
		typedef ListIterator<T> iterator;
		//...迭代器运算符重载内容

		node* _node;
	};
	
	template <class T>
	class list {
	public:
		typedef ListNode<T> node;
		typedef ListIterator<T> iterator;
		// ...内容

	private:
		node* _head;
		size_t _size;
	};

}

        我们将根据以上框架实现 list 类。

2. list 的实现

2.1 pop and push

        本篇中首先实现的为 list 中的 pop_back pop_front push_back push_front 这4个函数,对于这四个函数的作用分别为删除最后一个元素,删除第一个元素,尾插一个元素,头插一个元素。我们先实现 pop 函数,如下:

	void pop_back() {
		node* tail = _head->_prev;
		node* tail_prev = tail->_prev;
		_head->_prev = tail_prev;
		tail_prev->_next = _head;
		delete tail;
		tail = nullptr;
		_size--;
		/*erase(--end());*/
	}

	void pop_front() {
		node* cur = _head->_next;
		node* next = cur->_next;
		_head->_next = next;
		next->_prev = _head;
		delete cur;
		_size--;
		/*erase(begin());*/
	}

        以上删除的操作,其中的重点为熟悉双向循环链表的指向,删除一个元素,需要将前一个元素的指针的 next 指向下一个元素,将下一个元素的 prev 指向指向前一个,然后将当前指针删除。

        接下来实现 push 函数,对于 push 函数的实现,其中主要的要点还是需要熟悉一个新节点的插入,将最后一个结点的 next 指针指向新节点,将新节点的 next 指针指向头结点,将头结点的 prev 指针指向新节点,然后将新节点的 prev 指针指向最后一个结点,这样新结点就成了最后一个结点,对于头插也是一样的道理,只是实现的时候,我们需要注意其中的细节。具体的实现如下:

		void push_back(const T& data) {
		 先new出一个结点
		node* newnode = new node(data);
		// 找到尾结点,新结点的prev指向尾结点,新结点的next指向头结点
		node* tail = _head->_prev;
		newnode->_prev = tail;
		tail->_next = newnode;
		newnode->_next = _head;
		_head->_prev = newnode;
		_size++;
		/*insert(end(), data);*/
	}

	void push_front(const T& data) {
		node* newnode = new node(data);
		node* cur = _head->_next;
		_head->_next = newnode;
		newnode->_prev = _head;
		newnode->_next = cur;
		cur->_prev = newnode;
		_size++;
		/*insert(begin(), data);*/
	}

2.2 swap、empty、and size

        接下来我们将实现 swap empty size 函数,对于这几个函数来说,实现的逻辑也相对简单,实现的逻辑如下:

	void swap(list& ls) {
		std::swap(_head, ls._head);
		std::swap(_size, ls._size);
		// 换一种写法
	}

	size_t size() const {
		return _size;
	}

	bool empty() {
		return _head == _head->_next;
	}

        对于以上的代码,swap 的实现,只需要调用 std 中的 swap 函数即可,因为本身对于 list 的维护就是使用一个指针和一个 size,所以只需要交换即可。对于 empty 的实现,只需要判断哨兵位是否指向自己。

2.3 iterator、insert、erase

        从这段开始,我们将开始实现我们的迭代器,为什么要等到这段才实现 insert 和 erase 函数呢?因为这两个函数的参数都包括迭代器,所以我们需要先实现迭代器。对于迭代器的实现,其主要还是实现我们常使用的迭代器操作,比如对迭代器 ++ 指向下一个元素、对迭代器 -- 、指向前一个元素,迭代之间的比较 == != 判断是否要继续迭代下去,这些都需要使用运算符重载来实现,具体的实现方式如下:

	// 迭代器类
	template <class T>
	struct ListIterator {
	public:
		typedef ListNode<T> node;
		typedef ListIterator<T> iterator;
		//...迭代器运算符重载内容
		ListIterator(node* n)
			:_node(n)
		{}
	
		~ListIterator() {
			_node = nullptr;
		}
		// 无内置参数,表示前置++
		iterator& operator++() {
			_node = _node->_next;
			return *this;
		}
		// 有内置参数,后置++,返回拷贝的值,不能返回引用,tmp只是一个临时变量,会被析构
		iterator operator++(int) {
			iterator tmp(_node);
			_node = _node->_next;
			return tmp;
		}
	
		iterator& operator--() {
			_node = _node->_prev;
			return *this;
		}
	
		iterator operator--(int) {
			iterator tmp(_node);
			_node = _node->_prev;
			return tmp;
		}
	
		bool operator!=(const iterator& it) {
			return _node != it._node;
		}
	
		bool operator==(const iterator& it) {
			return _node == it._node;
		}
		
		T& operator*() {
			return _node->_data;
		}
	
		// it->->data
		T* operator->() {
			return &_node->_data;
		}
		node* _node;
	};

        如上,当我们实现 -- 的时候,就是返回当前位置的前一个指针,不过需要注意是前置 -- 还是后置 --,返回的值不一样,对于前置 ++ 和后置 ++ 也一样。然后是 == 与 != 我们同样也只需要返回结点指针的 == 和 != 的 bool 值,对于以上的解引用,我们只需要返回当前指针指向元素的引用即可。除了这些,我们还发现存在一个特殊的运算符重载:T* operator->() ,这个我们返回的为指针指向结点内容的地址,对于这个的使用如下:

struct A {
	int _a1;
	int _a2;

	A(int a1 = 0, int a2 = 0)
		:_a1(a1)
		, _a2(a2)
	{}
};

void Test02() {
	MyList::list<A> ls;
	ls.push_back({ 1,2 });
	ls.push_back({ 3,4 });
	ls.push_back({ 5,6 });
	ls.push_back({ 7,8 });
	ls.push_back({ 9,10 });
	auto it = ls.begin();
	while (it != ls.end()) {
		cout << it.operator->()->_a1 << " " << it.operator->()->_a2 << endl;
		cout << it->_a1 << " " << it->_a2 << endl;
		++it;
	}
}

        当我们在 list 中插入的元素中是一个包含多个元素的结构体的时候,这时候使用 *iterator 就不管用了,所以我们需要返回这个结构体的地址,然后在分别遍历,如上,但是按道理来说我们应该在运算符重载那里写两个 -> 但编译器为了加强可读性,将两个箭头优化为了一个。

        有了以上的迭代器,我们就可以开始实现我们的 insert 函数和 erase 函数了,对于 insert 函数来说,函数的功能为在指定位置前插入元素,返回值为新插入元素的位置,实现如下:

	iterator insert(iterator pos, const T& data) {
		// 在pos位置之前插入元素
		node* cur = pos._node;
		node* prev = cur->_prev;
		node* newnode = new node(data);
		cur->_prev = newnode;
		prev->_next = newnode;
		newnode->_prev = prev;
		newnode->_next = cur;
		_size++;
		return newnode;
	}

        对于 erase 函数来说,其主要功能为删除指定位置的元素,然后返回刚删除位置的下一个元素,实现原理如下:

	iterator erase(iterator pos) {
		node* cur = pos._node;
		node* next = cur->_next;
		node* prev = cur->_prev;
		prev->_next = next;
		next->_prev = prev;
		delete cur;
		_size--;
		return next;
	}

        实现以上后,我们还需要在 list 类中实现 begin 和 end 函数,实现如下:

	iterator end() {
		iterator tmp(_head);
		return tmp;
	}

	iterator begin() {
		iterator tmp(_head->_next);
		return tmp;
	}

2.4 const_iterator

        对于迭代器而言,以上我们只实现了普通的迭代器,但是当我们遇见被 const 修饰的 list 类的时候,我们还是需要使用 const_iterator,而对于 const_iterator 而言,我们希望的为不希望被 const 修饰的 list 类中的内容不被修改,所以我们可以在实现的时候,相对于普通迭代器只需要将 T& operator*() 和 T* operator->() 用 const 修饰即可,所以得出的 const 迭代器,如下:

	template <class T>
	struct ListConstIterator {
	public:
		typedef ListNode<T> node;
		typedef ListConstIterator<T> const_iterator;
		// 实现iterator的各种操作 前置++、--,后置++、--,==、!=
		// 对迭代器进行构造、构造函数
		ListConstIterator(node* n)
			:_node(n)
		{}

		~ListConstIterator() {
			_node = nullptr;
		}
		// 无内置参数,表示前置++
		const_iterator& operator++() {
			_node = _node->_next;
			return *this;
		}
		// 有内置参数,后置++,返回拷贝的值,不能返回引用,tmp只是一个临时变量,会被析构
		const_iterator operator++(int) {
			const_iterator tmp(_node);
			_node = _node->_next;
			return tmp;
		}

		const_iterator& operator--() {
			_node = _node->_prev;
			return *this;
		}

		const_iterator operator--(int) {
			const_iterator tmp(_node);
			_node = _node->_prev;
			return tmp;
		}

		bool operator!=(const const_iterator& it) {
			return _node != it._node;
		}

		bool operator==(const const_iterator& it) {
			return _node == it._node;
		}

		const T& operator*() const{
			return _node->_data;
		}

		// it->->data
		const T* operator->() const{
			return &_node->_data;
		}

		node* _node;
	};

2.5 模板 iterator

        对于以上两个迭代器而言,我们发现这两个迭代器存在很大的冗余,其中只有两个函数不一样,那么我们是否可以将其封装为一个模板呢?答案是可以的,我们可以将传引用和传地址的部分封装为一个模板参数,然后在 list 类中,将 T T& T* 和 T const T& const T* 分别传进去,实现如下:

	template <class T, class Ref, class Ptr>
	struct ListIterator {
	public:
		typedef ListNode<T> node;
		typedef ListIterator<T, Ref, Ptr> iterator;
		// 实现iterator的各种操作 前置++、--,后置++、--,==、!=
		// 对迭代器进行构造、构造函数
		ListIterator(node* n)
			:_node(n)
		{}

		// 未开空间的类,有无析构都无所谓
		~ListIterator() {
			_node = nullptr;
		}
		// 无内置参数,表示前置++
		iterator& operator++() {
			_node = _node->_next;
			return *this;
		}
		// 有内置参数,后置++,返回拷贝的值,不能返回引用,tmp只是一个临时变量,会被析构
		iterator operator++(int) {
			iterator tmp(_node);
			_node = _node->_next;
			return tmp;
		}

		iterator& operator--() {
			_node = _node->_prev;
			return *this;
		}

		iterator operator--(int) {
			iterator tmp(_node);
			_node = _node->_prev;
			return tmp;
		}

		bool operator!=(const iterator& it) {
			return _node != it._node;
		}

		bool operator==(const iterator& it) {
			return _node == it._node;
		}

		Ref operator*() {
			return _node->_data;
		}

		// it->->data
		Ptr operator->() {
			return &_node->_data;
		}

		node* _node;
	};

        我们在 list 类中使用该模板迭代器的形式如下:

2.6 默认成员函数

        最后我们在来实现我们的默认成员函数:构造、拷贝构造、赋值运算符重载、析构函数。首先我们先实现我们的构造和拷贝构造函数,使用构造只需要将我们的哨兵位的结点初始化即可,因为是双向循环链表,两个指针都指向指向自己。对于拷贝构造函数而言,我们实现的时候,同样也需要提前将哨兵位的结点空间开辟出来,然后使用范围 for 将每个元素尾插进入新的链表,实现如下:

	// 构造和拷贝构造功能相重叠的部分
	void empty_init() {
		_head = new node(T());
		_head->_next = _head;
		_head->_prev = _head;
		_size = 0;
	}

	list() {
		empty_init();
	}

	// 拷贝构造函数
	list(const list& ls) {
		empty_init();
		for (auto e : ls) {
			push_back(e);
		}
	}

        接着实现赋值运算符重载,对于赋值运算符重载函数而言,和拷贝构造类似,我们只需要在参数位置将传入的 list 拷贝构造,然后调用 swap 函数,交换参数的值即可,实现如下:

	list& operator=(list ls) {
		swap(ls);
		return *this;
	}

        最后则是我们的析构函数,对于析构函数而言,就是需要将所有的结点都释放掉,包括哨兵位,其实这个和 clear 函数的功能相重叠,只不过 clear 函数不需要将哨兵位给释放掉,所以我们只需要先实现 clear 函数,然后调用 clear 函数,在使用哨兵位,就可以实现析构函数,实现如下:

	void clear() {
		auto it = begin();
		while (it != end()) {
			it = erase(it);
		}
	}

	~list() {
		clear();
		delete _head;
		_head = nullptr;
	}

        至此,本篇实现的函数便完结了。

3. all code

namespace MyList {
	template <class T>
	struct ListNode {
		ListNode* _next;
		ListNode* _prev;
		T _data;
		// 结点的构造函数
		ListNode(const T& data)
			:_next(nullptr)
			, _prev(nullptr)
			, _data(data)
		{}
	};

	template <class T, class Ref, class Ptr>
	struct ListIterator {
	public:
		typedef ListNode<T> node;
		typedef ListIterator<T, Ref, Ptr> iterator;
		// 实现iterator的各种操作 前置++、--,后置++、--,==、!=
		// 对迭代器进行构造、构造函数
		ListIterator(node* n)
			:_node(n)
		{}

		// 未开空间的类,有无析构都无所谓
		~ListIterator() {
			_node = nullptr;
		}
		// 无内置参数,表示前置++
		iterator& operator++() {
			_node = _node->_next;
			return *this;
		}
		// 有内置参数,后置++,返回拷贝的值,不能返回引用,tmp只是一个临时变量,会被析构
		iterator operator++(int) {
			iterator tmp(_node);
			_node = _node->_next;
			return tmp;
		}

		iterator& operator--() {
			_node = _node->_prev;
			return *this;
		}

		iterator operator--(int) {
			iterator tmp(_node);
			_node = _node->_prev;
			return tmp;
		}

		bool operator!=(const iterator& it) {
			return _node != it._node;
		}

		bool operator==(const iterator& it) {
			return _node == it._node;
		}

		Ref operator*() {
			return _node->_data;
		}

		// it->->data
		Ptr operator->() {
			return &_node->_data;
		}

		node* _node;
	};

	template <class T>
	class list {
	public:
		typedef ListNode<T> node;
		typedef ListIterator<T, T&, T*> iterator;
		typedef ListIterator<T, const T&, const T*> const_iterator;
		// ...内容

		// 构造和拷贝构造功能相重叠的部分
		void empty_init() {
			_head = new node(T());
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;
		}

		list() {
			empty_init();
		}

		// 拷贝构造函数
		list(const list& ls) {
			empty_init();
			for (auto e : ls) {
				push_back(e);
			}
		}

		~list() {
			clear();
			delete _head;
			_head = nullptr;
		}

		void pop_back() {
			node* tail = _head->_prev;
			node* tail_prev = tail->_prev;
			_head->_prev = tail_prev;
			tail_prev->_next = _head;
			delete tail;
			tail = nullptr;
			_size--;
			/*erase(--end());*/
		}

		void clear() {
			auto it = begin();
			while (it != end()) {
				it = erase(it);
			}
		}

		void pop_front() {
			node* cur = _head->_next;
			node* next = cur->_next;
			_head->_next = next;
			next->_prev = _head;
			delete cur;
			_size--;
			/*erase(begin());*/
		}

		void push_back(const T& data) {
			 先new出一个结点
			node* newnode = new node(data);
			// 找到尾结点,新结点的prev指向尾结点,新结点的next指向头结点
			node* tail = _head->_prev;
			newnode->_prev = tail;
			tail->_next = newnode;
			newnode->_next = _head;
			_head->_prev = newnode;
			_size++;
			/*insert(end(), data);*/
		}

		void push_front(const T& data) {
			node* newnode = new node(data);
			node* cur = _head->_next;
			_head->_next = newnode;
			newnode->_prev = _head;
			newnode->_next = cur;
			cur->_prev = newnode;
			_size++;
			/*insert(begin(), data);*/
		}

		void swap(list& ls) {
			std::swap(_head, ls._head);
			std::swap(_size, ls._size);
			// 换一种写法
		}

		size_t size() const {
			return _size;
		}

		bool empty() {
			return _head == _head->_next;
		}

		iterator insert(iterator pos, const T& data) {
			// 在pos位置之前插入元素
			node* cur = pos._node;
			node* prev = cur->_prev;
			node* newnode = new node(data);
			cur->_prev = newnode;
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			_size++;
			return newnode;
		}

		iterator erase(iterator pos) {
			node* cur = pos._node;
			node* next = cur->_next;
			node* prev = cur->_prev;
			prev->_next = next;
			next->_prev = prev;
			delete cur;
			_size--;
			return next;
		}

		iterator end() {
			iterator tmp(_head);
			return tmp;
		}

		iterator begin() {
			iterator tmp(_head->_next);
			return tmp;
		}

	private:
		node* _head;
		size_t _size;
	};
}

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

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

相关文章

Sy8网络管理命令(ubuntu23.10和centos8)

前言、 本次实验主要是扩展学习&#xff0c;不仅限在课本的内容。毕竟课本的内容太过于陈旧了。需要的童鞋看看。 说明&#xff1a;&#xff08;书本中sy9”第3.实验内容“大家还是要做下。&#xff09; 1、使用ubuntu做实验的童鞋只要看第二、三、四、七章节的部分内容。 2、使…

2024年4月计算机视觉论文推荐

本文将整理4月发表的计算机视觉的重要论文&#xff0c;重点介绍了计算机视觉领域的最新研究和进展&#xff0c;包括图像识别、视觉模型优化、生成对抗网络(gan)、图像分割、视频分析等各个子领域 扩散模型 1、Tango 2: Aligning Diffusion-based Text-to-Audio Generations th…

基于遗传算法的TSP算法(matlab实现)

一、理论基础 TSP(traveling salesman problem,旅行商问题)是典型的NP完全问题&#xff0c;即其最坏情况下的时间复杂度随着问题规模的增大按指数方式增长&#xff0c;到目前为止还未找到一个多项式时间的有效算法。TSP问题可描述为&#xff1a;已知n个城市相互之间的距离&…

25计算机考研院校数据分析 | 南京大学

南京大学&#xff08;Nanjing University&#xff09;&#xff0c;简称“南大”&#xff0c;是中华人民共和国教育部直属、中央直管副部级建制的全国重点大学&#xff0c;国家首批“双一流”、“211工程”、“985工程”重点建设高校&#xff0c;入选首批“珠峰计划”、“111计划…

无人机+巡飞弹:“柳叶刀”巡飞弹技术详解

“柳叶刀”巡飞弹技术是一种结合了无人机和巡飞弹的先进武器系统&#xff0c;由俄罗斯ZalaAero公司研制&#xff0c;首次公开亮相是在2019年的俄罗斯军队装备展上。该系统以其高度的灵活性和精确打击能力&#xff0c;在现代战场上扮演着重要角色。 系统组成&#xff1a;柳叶刀巡…

MFC实现ini配置文件的读取

MFC实现 ini 配置文件的读取1 实现的功能&#xff1a;点击导入配置文件按钮可以在旁边编辑框中显示配置文件的路径&#xff0c;以及在下面的编辑框中显示配置文件的内容。 1. 显示配置文件内容的编辑框设置 对于显示配置文件内容的 Edit Contorl 编辑框的属性设置如下&#x…

自制音频格式二维码的方法,适合多种音频格式使用

现在可以通过二维码的方法来传递音频文件是很常用的一种方式&#xff0c;可以将单个或者多个音频放入一个二维码&#xff0c;通过手机扫码来调取云端储存的音频文件来播放内容&#xff0c;这样可以让多人同时扫码获取内容&#xff0c;提升传播速度。 音频二维码制作的方法也比…

纵览2024年:排名靠前的项目管理软件一览!

时间飞逝&#xff0c;2024年已经过去近半&#xff0c;让我们来盘点2024年排名靠前的项目管理软件&#xff0c;项目管理软件排行榜&#xff0c;本次上榜的项目管理软件有Zoho Projects、Microsoft Project、Nifty、Smartsheet、ClickUp。 一、项目管理软件排行榜 1.Zoho Projec…

航空企业数字化解决方案(207页PPT)

一、资料描述 航空企业数字化解决方案是一项针对航空公司在数字化转型过程中所面临挑战的全面应对策略&#xff0c;旨在通过先进的信息技术提升航空企业的运营效率、客户服务水平以及市场竞争力。这份207页的PPT详细介绍了航空企业数字化的各个方面&#xff0c;包括关键技术的…

解决主机有网络但虚拟机没网络连接问题----记录

问题描述&#xff1a;主机Windows有网络但虚拟机Linux没有网络 1.使用ifconfig 命令查看Linux网络IP 发现只有lo本地回环网卡ip并没有真实网卡IP 2.查看本地所有网卡 终端输入 ip link show 结果&#xff1a;虚拟机上还有一个ens33 真实的网卡驱动&#xff0c; 解决办法&…

Aurora-64B/10B、XDMA与DDR结合设计高速数据流通路设计/Aurora光纤设计/XDMA读取DDR设计/基于FPGA的高速数据传输设计

因最近想通过FPGA把数据从光纤传到PC&#xff0c;借此机会和大家一起学习Aurora、XDMA结合DDR 制作不易&#xff0c;记得三连哦&#xff0c;给我动力&#xff0c;持续更新&#xff01;&#xff01;&#xff01; 完整工程文件下载&#xff1a;XDMA读写DDR工程 提取码&…

数据结构——二叉树练习(深搜广搜)

数据结构——二叉树练习 路径之和深度优先算法和广度优先算法二叉搜索树判断一棵二叉树是否为搜索二叉树和完全二叉树 我们今天来看二叉树的习题&#xff1a; 路径之和 https://leetcode.cn/problems/path-sum-ii/ 这是一个典型的回溯&#xff0c;深度优先算法的题&#xff0c…

Docker镜像和容器操作

目录 一.Docker镜像创建与操作 1. 搜索镜像 2. 获取镜像 3. 镜像加速下载 4. 查看镜像信息 5. 查看下载的镜像文件信息 ​编辑6. 查看下载到本地的所有镜像 7. 根据镜像的唯一标识ID号&#xff0c;获取镜像详细信息 8. 为本地的镜像添加新的标签 9. 删除镜像 10. 存入…

【1731】jsp 房租跟踪监控管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 房租跟踪监控管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysq…

【uniapp/ucharts】采用 uniapp 框架的 h5 应用使用 ucharts(没有 uni_modules)

这种情况无法直接从 dcloud 平台上一键下载导入&#xff0c;所以应该在官网推荐的 git 仓库去单独下载&#xff1a; https://gitee.com/uCharts/uCharts/tree/master/uni-app/uCharts-%E7%BB%84%E4%BB%B6/qiun-data-charts(%E9%9D%9Euni_modules) 下载的文件是如图所示的路径&…

AI大模型探索之路-训练篇2:大语言模型预训练基础认知

文章目录 前言一、预训练流程分析二、预训练两大挑战三、预训练网络通信四、预训练数据并行五、预训练模型并行六、预训练3D并行七、预训练代码示例总结 前言 在人工智能的宏伟蓝图中&#xff0c;大语言模型&#xff08;LLM&#xff09;的预训练是构筑智慧之塔的基石。预训练过…

如何将web content项目导入idea并部署到tomcat

将Web Content项目导入IntelliJ IDEA并部署到Tomcat主要涉及以下几个步骤&#xff1a; 1. 导入Web Content项目 打开IntelliJ IDEA。选择“File” -> “New” -> “Project from Existing Sources…”。浏览到你的Web Content项目的文件夹&#xff0c;并选择它。Intell…

数据结构(C):时间复杂度和空间复杂度

目录 &#x1f680; 0.前言 &#x1f680; 1.为何会有时间复杂度和空间复杂度的概念 &#x1f680; 2.时间复杂度 2.1初步时间复杂度 2.2大O表示法 2.2.1.O&#xff08;N*N&#xff09; 2.2.2.O&#xff08;N&#xff09; 2.2.3.O&#xff08;1&#xff09; 2.3最坏情况…

【Qt】error LNK2001: 无法解析的外部符号

参考&#xff1a;Qt/VS LNK2019/LNK2001&#xff1a;无法解析的外部符号_qt lnk2001无法解析的外部符号-CSDN博客 微软官方报错文档-链接器工具错误 LNK2019 __declspec error LNK2001: 无法解析的外部符号 "__declspec(dllimport) 原因 以这种为前缀的基本上跟库相关…

微信小程序:11.本地生活小程序制作

开发工具&#xff1a; 微信开发者工具apifox进行创先Mock 项目初始化 新建小程序项目输入ID选择不使用云开发&#xff0c;js传统模版在project.private.config中setting配置项中配置checkinalidKey&#xff1a;false 梳理项目结构 因为该项目有三个tabbar所以我们要创建三…