C++/C++11中std::deque的使用

std::deque是双端队列,可以高效的在头尾两端插入和删除元素,在std::deque两端插入和删除并不会使其它元素的指针或引用失效。在接口上和std::vector相似。与sdk::vector相反,std::deque中的元素并非连续存储:典型的实现是使用一个单独分配的固定大小数组的序列。std::deque的存储空间会自动按需扩大和缩小。扩大std::deque比扩大std::vector要便宜,因为它不涉及到现有元素复制到新的内存位置。

双端队列(Double-ended queue,缩写为Deque)是一个大小可以动态变化(Dynamic size)且可以在两端扩展或收缩的顺序容器。顺序容器中的元素按照严格的线性顺序排序。可以通过元素在序列中的位置访问对应的元素。不同的库可能会按不同的方式来实现双端队列,通常实现为某种形式的动态数组。但不管通过哪种方式,双端队列都允许通过随机迭代器直接访问各个元素,且内部的存储空间会按需求自动地扩展或收缩。容器实际分配的内存数超过容纳当前所有有效元素所需的,因为额外的内存将被未来增长的部分所使用。就因为这点,当插入元素时,容器不需要太频繁地分配内存。因此,双端队列提供了类似向量(std::vector)的功能,且不仅可以在容器末尾,还可以在容器开头高效地插入或删除元素。但是,相比向量,双端队列不保证内部的元素是按连续的存储空间存储的,因此,不允许对指针直接做偏移操作来直接访问元素。在内部,双端队列与向量的工作方式完全不同:向量使用单数组数据结构,在元素增加的过程中,需要偶尔的内存重分配,而双端队列中的元素被零散地保存在不同的存储块中,容器内部会保存一些必要的数据使得可以以恒定时间及一个统一的顺序接口直接访问任意元素。因此,双端队列的内部实现比向量的稍稍复杂一点,但这也使得它在一些特定环境下可以更高效地增长,特别是对于非常长的序列,内存重分配的代价是及其高昂的。对于大量涉及在除了起始或末尾以外的其它任意位置插入或删除元素的操作,相比列表(std::list)及正向列表(std::forward_list),deque 所表现出的性能是极差的,且操作前后的迭代器、引用的一致性较低。

A deque is very much like a vector: like vector, it is a sequence that supports random access to elements, constant time insertion and removal of elements at the end of these quence, and linear time insertion and removal of elements in the middle. The main way in which deque differs from vector is that deque also supports constant time insertion and removal of elements at the beginning of the sequence. Additionally, deque does not have any member functions analogous to vector's capacity() and reserve(), and does not provide any of the guarantees on iterator validity that are associated with those member functions.

 

一个容器就是一些特定类型对象的集合。顺序容器(sequential container)为程序员提供了控制元素存储和访问顺序的能力。这种顺序不依赖于元素的值,而是与元素加入容器时的位置相对应。

         标准库中的顺序容器包括:

         (1)、vector:可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢。

         (2)、deque:双端队列。支持快速随机访问。在头尾位置插入/删除速度很快。

         (3)、list:双向链表。只支持双向顺序访问。在list中任何位置进行插入/删除操作速度都很快。

         (4)、forward_list:单向链表。只支持单向顺序访问。在链表任何位置进行插入/删除操作速度都很快。

         (5)、array:固定大小数组。支持快速随机访问。不能添加或删除元素。

         (6)、string:与vector相似的容器,但专门用于保存字符。随机访问快。在尾部插入/删除速度快。

         除了固定大小的array外,其它容器都提供高效、灵活的内存管理。我们可以添加和删除元素,扩张和收缩容器的大小。容器保存元素的策略对容器操作的效率有着固定的,有时是重大的影响。在某些情况下,存储策略还会影响特定容器是否支持特定操作。

         例如,string和vector将元素保存在连续的内存空间中。由于元素是连续存储的,由元素的下标来计算其地址是非常快速的。但是,在这两种容器的中间位置添加或删除元素就会非常耗时:在一次插入或删除操作后,需要移动插入/删除位置之后的所有元素,来保持连续存储。而且,添加一个元素有时可能还需要分配额外的存储空间。在这种情况下,每个元素都必须移动到新的存储空间中。

         list和forward_list两个容器的设计目的是令容器任何位置的添加和删除操作都很快速。作为代价,这两个容器不支持元素的随机访问:为了访问一个元素,我们只能遍历整个容器。而且,与vector、deque和array相比,这两个容器的额外内存开销也很大。

         deque是一个更为复杂的数据结构。与string和vector类似,deque支持快速的随机访问。与string和vector一样,在deque的中间位置添加或删除元素的代价(可能)很高。但是,在deque的两端添加或删除元素都是很快的,与list或forward_list添加删除元素的速度相当。

         forward_list和array是新C++标准增加的类型。与内置数组相比,array是一个种更安全、更容易使用的数组类型。与内置数组类似,array对象的大小是固定的。因此,array不支持添加和删除元素以及改变容器大小的操作。forward_list的设计目标是达到与最好的手写的单向链表数据结构相当的性能。因此,forward_list没有size操作,因为保存或计算其大小就会比手写链表多出额外的开销。对其他容器而言,size保证是一个快速的常量时间的操作。

         通常,使用vector是最好的选择,除法你有很好的理由选择其他容器。

         以下是一些选择容器的基本原则:

         (1)、除法你有很好的理由选择其他容器,否则应该使用vector;

         (2)、如果你的程序有很多小的元素,且空间的额外开销很重要,则不要使用list或forward_list;

         (3)、如果程序要求随机访问元素,应使用vector或deque;

         (4)、如果程序要求在容器的中间插入或删除元素,应使用list或forward_list;

(5)、如果程序需要在头尾位置插入或删除元素,但不会在中间位置进行插入或删除操作,则使用deque;

(6)、如果程序只有在读取输入时才需要在容器中间位置插入元素,随后需要随机访问元素,则:首先,确定是否真的需要在容器中间位置添加元素。当处理输入数据时,通常可以很容器地向vector追加数据,然后再调用标准库的sort函数来重排容器中的元素,从而避免在中间位置添加元素。如果必须在中间位置插入元素,考虑在输入阶段使用list,一旦输入完成,将list中的内容拷贝到一个vector中。

如果你不确定应该使用哪种容器,那么可以在程序中只使用vector和list公共的操作:使用迭代器,不使用下标操作,避免随机访问。这样,在必要时选择使用vector或list都很方便。

一般来说,每个容器都定义在一个头文件中,文件名与类型名相同。即,deque定义在头文件deque中,list定义在头文件list中,以此类推。容器均定义为模板类。

顺序容器几乎可以保存任意类型的元素。特别是,我们可以定义一个容器,其元素的类型是另一个容器。这种容器的定义与任何其他容器类型完全一样:在尖括号中指定元素类型(此种情况下,是另一种容器类型)。

除了顺序容器外,标准库还定义了三个顺序容器适配器:stack、queue和priority_queue。适配器(adaptor)是标准库中的一个通用概念。容器、迭代器和函数都有适配器。本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。一个容器适配器接受一种已有的容器类型,使其行为看起来像一种不同的类型。


下面是从其他文章中copy的测试代码,详细内容介绍可以参考对应的reference:

#include "deque.hpp"
#include <iostream>
#include <deque>
#include <vector>


// https://msdn.microsoft.com/en-us/library/22a9t119.aspx
int test_deque_2()
{
{ // deque::at: Returns a reference to the element at a specified location in the deque.
	using namespace std;
	deque <int> c1;

	c1.push_back(10);
	c1.push_back(20);

	const int& i = c1.at(0);
	int& j = c1.at(1);
	cout << "The first element is " << i << endl;
	cout << "The second element is " << j << endl;
}

{ // deque::back: Returns a reference to the last element of the deque.
	using namespace std;
	deque <int> c1;

	c1.push_back(10);
	c1.push_back(11);

	int& i = c1.back();
	const int& ii = c1.front();

	cout << "The last integer of c1 is " << i << endl; // 11
	i--;
	cout << "The next-to-last integer of c1 is " << ii << endl; // 10
	cout << "The last integer of c1 is " << c1.back() << endl; // 10
}

{ // deque::clear: Erases all the elements of a deque.
	using namespace std;
	deque <int> c1;

	c1.push_back(10);
	c1.push_back(20);
	c1.push_back(30);

	cout << "The size of the deque is initially " << c1.size() << endl;
	c1.clear();
	cout << "The size of the deque after clearing is " << c1.size() << endl;
}

{ // deque::const_reference: A type that provides a reference to a const element stored in a deque for reading and performing const operations
	using namespace std;
	deque <int> c1;

	c1.push_back(10);
	c1.push_back(20);

	const deque <int> c2 = c1;
	const int &i = c2.front();
	const int &j = c2.back();
	cout << "The first element is " << i << endl;
	cout << "The second element is " << j << endl;

	// The following line would cause an error as c2 is const
	// c2.push_back( 30 );
}

{ // deque::crbegin: Returns a const iterator to the first element in a reversed deque
	using namespace std;
	deque <int> v1;
	deque <int>::iterator v1_Iter;
	deque <int>::const_reverse_iterator v1_rIter;

	v1.push_back(1);
	v1.push_back(2);

	v1_Iter = v1.begin();
	cout << "The first element of deque is "
		<< *v1_Iter << "." << endl;

	v1_rIter = v1.crbegin();
	cout << "The first element of the reversed deque is "
		<< *v1_rIter << "." << endl;
}

{ // deque::emplace: Inserts an element constructed in place into the deque at a specified position.
	using namespace std;
	deque <int> v1;
	deque <int>::iterator Iter;

	v1.push_back(10);
	v1.push_back(20);
	v1.push_back(30);

	cout << "v1 =";
	for (Iter = v1.begin(); Iter != v1.end(); Iter++)
		cout << " " << *Iter;
	cout << endl;

	// initialize a deque of deques by moving v1  
	deque < deque <int> > vv1;

	vv1.emplace(vv1.begin(), move(v1));
	if (vv1.size() != 0 && vv1[0].size() != 0) {
		cout << "vv1[0] =";
		for (Iter = vv1[0].begin(); Iter != vv1[0].end(); Iter++)
			cout << " " << *Iter;
		cout << endl;
	}
}

	return 0;
}

/
// reference: http://www.cplusplus.com/reference/deque/deque/
int test_deque_1()
{
{ // deque::deque: Construct deque container
	unsigned int i;

	// constructors used in the same order as described above:
	std::deque<int> first;                                // empty deque of ints
	std::deque<int> second(4, 100);                       // four ints with value 100
	std::deque<int> third(second.begin(), second.end());  // iterating through second
	std::deque<int> fourth(third);                       // a copy of third

	// the iterator constructor can be used to copy arrays:
	int myints[] = { 16, 2, 77, 29 };
	std::deque<int> fifth(myints, myints + sizeof(myints) / sizeof(int));

	std::cout << "The contents of fifth are:";
	for (std::deque<int>::iterator it = fifth.begin(); it != fifth.end(); ++it)
		std::cout << ' ' << *it; // 16 2 77 29
	std::cout << '\n';
}

{ // deque::assign: Assigns new contents to the deque container,
  // replacing its current contents, and modifying its size accordingly.

	std::deque<int> first;
	std::deque<int> second;
	std::deque<int> third;

	first.assign(7, 100);             // 7 ints with a value of 100

	std::deque<int>::iterator it;
	it = first.begin() + 1;

	second.assign(it, first.end() - 1); // the 5 central values of first

	int myints[] = { 1776, 7, 4 };
	third.assign(myints, myints + 3);   // assigning from array.

	std::cout << "Size of first: " << int(first.size()) << '\n'; // 7
	std::cout << "Size of second: " << int(second.size()) << '\n'; // 5
	std::cout << "Size of third: " << int(third.size()) << '\n'; // 3
}

{ // deque::at: Returns a reference to the element at position n in the deque container object.
	std::deque<unsigned> mydeque(10);   // 10 zero-initialized unsigneds

	// assign some values:
	for (unsigned i = 0; i < mydeque.size(); i++)
		mydeque.at(i) = i;

	std::cout << "mydeque contains:";
	for (unsigned i = 0; i < mydeque.size(); i++)
		std::cout << ' ' << mydeque.at(i); // 0 1 2 3 4 5 6 7 8 9
	std::cout << '\n';
}

{ // deque::back: Returns a reference to the last element in the container.
  // deque::push_back: Adds a new element at the end of the deque container,
  // after its current last element. The content of val is copied (or moved) to the new element
	std::deque<int> mydeque;
	mydeque.push_back(10);

	while (mydeque.back() != 0)
		mydeque.push_back(mydeque.back() - 1);

	std::cout << "mydeque contains:";
	for (std::deque<int>::iterator it = mydeque.begin(); it != mydeque.end(); ++it)
		std::cout << ' ' << *it;
	std::cout << '\n';
}

{ // deque::begin: Return iterator to beginning
  // deque::end: Return iterator to end
	std::deque<int> mydeque;
	for (int i = 1; i <= 5; i++) mydeque.push_back(i);

	std::cout << "mydeque contains:";
	std::deque<int>::iterator it = mydeque.begin();

	while (it != mydeque.end())
		std::cout << ' ' << *it++;
	std::cout << '\n';
}

{ // deque::cbegin: c++11, Return const_iterator to beginning
  // deque::cend: c++11, Return const_iterator to end
	std::deque<int> mydeque = { 10, 20, 30, 40, 50 };

	std::cout << "mydeque contains:";
	for (auto it = mydeque.cbegin(); it != mydeque.cend(); ++it)
		std::cout << ' ' << *it;
	std::cout << '\n';
}

{ // deque::clear: Clear content
	unsigned int i;
	std::deque<int> mydeque;
	mydeque.push_back(100);
	mydeque.push_back(200);
	mydeque.push_back(300);

	std::cout << "mydeque contains:";
	for (std::deque<int>::iterator it = mydeque.begin(); it != mydeque.end(); ++it)
		std::cout << ' ' << *it;
	std::cout << '\n';

	mydeque.clear();
	mydeque.push_back(1101);
	mydeque.push_back(2202);

	std::cout << "mydeque contains:";
	for (std::deque<int>::iterator it = mydeque.begin(); it != mydeque.end(); ++it)
		std::cout << ' ' << *it;
	std::cout << '\n';
}

{ // deque::crbegin: c++11, Return const_reverse_iterator to reverse beginning
  // deque::crend: c++11, Return const_reverse_iterator to reverse end
	std::deque<int> mydeque = { 1, 2, 3, 4, 5 };

	std::cout << "mydeque backwards:";
	for (auto rit = mydeque.crbegin(); rit != mydeque.crend(); ++rit)
		std::cout << ' ' << *rit;
	std::cout << '\n';
}

{ // deque::emplace: c++11, Construct and insert element
	std::deque<int> mydeque = { 10, 20, 30 };

	auto it = mydeque.emplace(mydeque.begin() + 1, 100);
	mydeque.emplace(it, 200);
	mydeque.emplace(mydeque.end(), 300);

	std::cout << "mydeque contains:";
	for (auto& x : mydeque)
		std::cout << ' ' << x;
	std::cout << '\n';
}

{ // deque::emplace_back: c++11, Construct and insert element at the end
	std::deque<int> mydeque = { 10, 20, 30 };

	mydeque.emplace_back(100);
	mydeque.emplace_back(200);

	std::cout << "mydeque contains:";
	for (auto& x : mydeque)
		std::cout << ' ' << x;
	std::cout << '\n';
}

{ // deque::emplace_front: c++11, Construct and insert element at beginning
	std::deque<int> mydeque = { 10, 20, 30 };

	mydeque.emplace_front(111);
	mydeque.emplace_front(222);

	std::cout << "mydeque contains:";
	for (auto& x : mydeque)
		std::cout << ' ' << x;
	std::cout << '\n';
}

{ // deque::empty: Test whether container is empty
	std::deque<int> mydeque;
	int sum(0);

	for (int i = 1; i <= 10; i++) mydeque.push_back(i);

	while (!mydeque.empty()) {
		sum += mydeque.front();
		mydeque.pop_front();
	}

	std::cout << "total: " << sum << '\n';
}

{ // deque::erase: Erase elements
	std::deque<int> mydeque;

	// set some values (from 1 to 10)
	for (int i = 1; i <= 10; i++) mydeque.push_back(i);

	// erase the 6th element
	mydeque.erase(mydeque.begin() + 5);

	// erase the first 3 elements:
	mydeque.erase(mydeque.begin(), mydeque.begin() + 3);

	std::cout << "mydeque contains:";
	for (std::deque<int>::iterator it = mydeque.begin(); it != mydeque.end(); ++it)
		std::cout << ' ' << *it;
	std::cout << '\n';
}

{ // deque::front: Access first element, Returns a reference to the first element in the deque containe
  // deque::push_front: Inserts a new element at the beginning of the deque container,
  // right before its current first element. The content of val is copied (or moved) to the inserted element
	std::deque<int> mydeque;

	mydeque.push_front(77);
	mydeque.push_back(20);

	mydeque.front() -= mydeque.back();

	std::cout << "mydeque.front() is now " << mydeque.front() << '\n';
}

{ // deque::get_allocator: Returns a copy of the allocator object associated with the deque object
	std::deque<int> mydeque;
	int * p;
	unsigned int i;

	// allocate an array with space for 5 elements using deque's allocator:
	p = mydeque.get_allocator().allocate(5);

	// construct values in-place on the array:
	for (i = 0; i < 5; i++) mydeque.get_allocator().construct(&p[i], i);

	std::cout << "The allocated array contains:";
	for (i = 0; i < 5; i++) std::cout << ' ' << p[i];
	std::cout << '\n';

	// destroy and deallocate:
	for (i = 0; i < 5; i++) mydeque.get_allocator().destroy(&p[i]);
	mydeque.get_allocator().deallocate(p, 5);
}

{ // deque::insert: Insert elements
	std::deque<int> mydeque;

	// set some initial values:
	for (int i = 1; i < 6; i++) mydeque.push_back(i); // 1 2 3 4 5

	std::deque<int>::iterator it = mydeque.begin();
	++it;

	it = mydeque.insert(it, 10);                  // 1 10 2 3 4 5
	// "it" now points to the newly inserted 10

	mydeque.insert(it, 2, 20);                     // 1 20 20 10 2 3 4 5
	// "it" no longer valid!

	it = mydeque.begin() + 2;

	std::vector<int> myvector(2, 30);
	mydeque.insert(it, myvector.begin(), myvector.end());
	// 1 20 30 30 20 10 2 3 4 5

	std::cout << "mydeque contains:";
	for (it = mydeque.begin(); it != mydeque.end(); ++it)
		std::cout << ' ' << *it;
	std::cout << '\n';
}

{ // deque::max_size: Return maximum size
	unsigned int i;
	std::deque<int> mydeque;

	std::cout << "Enter number of elements: ";
	i = 100; //std::cin >> i;

	if (i < mydeque.max_size()) mydeque.resize(i);
	else std::cout << "That size exceeds the limit.\n";
	fprintf(stderr, "max size: %d\n", mydeque.max_size());
}

{ // deque::operator=: Assigns new contents to the container, replacing its current contents, and modifying its size accordingly
	std::deque<int> first(3);    // deque with 3 zero-initialized ints
	std::deque<int> second(5);   // deque with 5 zero-initialized ints

	second = first;
	first = std::deque<int>();

	std::cout << "Size of first: " << int(first.size()) << '\n';
	std::cout << "Size of second: " << int(second.size()) << '\n';
}

{ // deque::operator[]: Returns a reference to the element at position n in the deque container
	std::deque<int> mydeque(10);   // 10 zero-initialized elements
	std::deque<int>::size_type sz = mydeque.size();

	// assign some values:
	for (unsigned i = 0; i < sz; i++) mydeque[i] = i;

	// reverse order of elements using operator[]:
	for (unsigned i = 0; i < sz / 2; i++) {
		int temp;
		temp = mydeque[sz - 1 - i];
		mydeque[sz - 1 - i] = mydeque[i];
		mydeque[i] = temp;
	}

	// print content:
	std::cout << "mydeque contains:";
	for (unsigned i = 0; i < sz; i++)
		std::cout << ' ' << mydeque[i];
	std::cout << '\n';
}

{ // deque::pop_back: Removes the last element in the deque container, effectively reducing the container size by one
	std::deque<int> mydeque;
	int sum(0);
	mydeque.push_back(10);
	mydeque.push_back(20);
	mydeque.push_back(30);

	while (!mydeque.empty()) {
		sum += mydeque.back();
		mydeque.pop_back();
	}

	std::cout << "The elements of mydeque add up to " << sum << '\n';
}

{ // deque::pop_front: Removes the first element in the deque container, effectively reducing its size by one.
	std::deque<int> mydeque;

	mydeque.push_back(100);
	mydeque.push_back(200);
	mydeque.push_back(300);

	std::cout << "Popping out the elements in mydeque:";
	while (!mydeque.empty()) {
		std::cout << ' ' << mydeque.front();
		mydeque.pop_front();
	}

	std::cout << "\nThe final size of mydeque is " << int(mydeque.size()) << '\n';
}

{ // deque::rbegin: Returns a reverse iterator pointing to the last element in the container
	// deque::rend: Returns a reverse iterator pointing to the theoretical element preceding the first element in the deque container
	std::deque<int> mydeque(5);  // 5 default-constructed ints

	std::deque<int>::reverse_iterator rit = mydeque.rbegin();

	int i = 0;
	for (rit = mydeque.rbegin(); rit != mydeque.rend(); ++rit)
		*rit = ++i;

	std::cout << "mydeque contains:";
	for (std::deque<int>::iterator it = mydeque.begin(); it != mydeque.end(); ++it)
		std::cout << ' ' << *it;
	std::cout << '\n';
}

{ // deque::resize: Resizes the container so that it contains n elements
	std::deque<int> mydeque;
	std::deque<int>::iterator it;

	// set some initial content:
	for (int i = 1; i < 10; ++i) mydeque.push_back(i);

	mydeque.resize(5);
	mydeque.resize(8, 100);
	mydeque.resize(12);

	std::cout << "mydeque contains:";
	for (std::deque<int>::iterator it = mydeque.begin(); it != mydeque.end(); ++it)
		std::cout << ' ' << *it;
	std::cout << '\n';
}

{ // deque::shrink_to_fit: c++11, Requests the container to reduce its memory usage to fit its size.
	// deque::size: Returns the number of elements in the deque container
	std::deque<int> mydeque(100);
	std::cout << "1. size of mydeque: " << mydeque.size() << '\n';

	mydeque.resize(10);
	std::cout << "2. size of mydeque: " << mydeque.size() << '\n';

	mydeque.shrink_to_fit();
	fprintf(stderr, "3. size of mydeque: %d\n", mydeque.size());
}

{ // deque::swap: Exchanges the content of the container by the content of x,
	// which is another deque object containing elements of the same type. Sizes may differ.
	unsigned int i;
	std::deque<int> foo(3, 100);   // three ints with a value of 100
	std::deque<int> bar(5, 200);   // five ints with a value of 200

	foo.swap(bar);

	std::cout << "foo contains:";
	for (std::deque<int>::iterator it = foo.begin(); it != foo.end(); ++it)
		std::cout << ' ' << *it;
	std::cout << '\n';

	std::cout << "bar contains:";
	for (std::deque<int>::iterator it = bar.begin(); it != bar.end(); ++it)
		std::cout << ' ' << *it;
	std::cout << '\n';
}

{ // relational operators: compare
	std::deque<int> foo(3, 100);   // three ints with a value of 100
	std::deque<int> bar(2, 200);   // two ints with a value of 200

	if (foo == bar) std::cout << "foo and bar are equal\n";
	if (foo != bar) std::cout << "foo and bar are not equal\n";
	if (foo< bar) std::cout << "foo is less than bar\n";
	if (foo> bar) std::cout << "foo is greater than bar\n";
	if (foo <= bar) std::cout << "foo is less than or equal to bar\n";
	if (foo >= bar) std::cout << "foo is greater than or equal to bar\n";
}

{ // std::swap: The contents of container x are exchanged with those of y.
  // Both container objects must be of the same type (same template parameters), although sizes may differ
	unsigned int i;
	std::deque<int> foo(3, 100);   // three ints with a value of 100
	std::deque<int> bar(5, 200);   // five ints with a value of 200

	swap(foo, bar);

	std::cout << "foo contains:";
	for (std::deque<int>::iterator it = foo.begin(); it != foo.end(); ++it)
		std::cout << ' ' << *it;
	std::cout << '\n';

	std::cout << "bar contains:";
	for (std::deque<int>::iterator it = bar.begin(); it != bar.end(); ++it)
		std::cout << ' ' << *it;
	std::cout << '\n';
}

	return 0;
}

GitHubhttps://github.com/fengbingchun/Messy_Test

  • 10
    点赞
  • 79
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值