首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

C++效率掌握之STL库:vector函数全解

  • 25-02-20 13:21
  • 4015
  • 10243
blog.csdn.net

文章目录

  • 1.为什么要学习vector?什么是vector?
  • 2.vector类对象的常见构造
  • 3.vector类对象的容量操作
  • 4.vector类对象的迭代器
  • 5.vector类对象的元素修改
  • 6.vector类对象的元素访问
  • 7.vector迭代器失效问题
  • 希望读者们多多三连支持
  • 小编会继续更新
  • 你们的鼓励就是我前进的动力!

本篇是 STL 库专题之 vector ,该类在算法题中广泛应用,既有数组的特性,又有简单的遍历,增删查改的操作函数,大大减少了传统数组的繁琐

1.为什么要学习vector?什么是vector?

vector 是标准模板库(STL)提供的一个容器类,它是动态数组的一种实现。这意味着它可以像普通数组一样存储一组相同类型的元素,并且能根据需要自动调整自身的大小,例如,你可以创建一个存储整数的 vector,然后不断往里面添加或删除元素,它会自动管理内存空间

vector 类是和 STL 库一起问世的,string 函数是在 STL 库之前创造的,为了一致性简便性,vector 、list 等类都减少了一部分不必要的函数,也将 string 加入了 STL 库

在这里插入图片描述

vector 的主要特征可总结为:

  1. vector 是表示可变大小数组的序列容器。
  2. 就像数组一样,vector 也采用的连续存储空间来存储元素。也就是意味着可以采用下标对 vector 的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理
  3. 本质讲,vector 使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector 并不会每次都重新分配大小
  4. vector 分配空间策略:vector 会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的
  5. 因此,vector 占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长
  6. 与其它动态序列容器相比(deque,list and forward_list),vector 在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起 list 和 forward_list 统一的迭代器和引用更好

2.vector类对象的常见构造

在这里插入图片描述

vector作为一个类也有构造函数,析构函数,=运算符重载,我们重点介绍构造函数里的功能

函数名功能说明
vector()无参构造
vector(size_type n, const value_type& val = value_type())构造并初始化n个val
vector (const vector& x)拷贝构造
vector (InputIterator first, InputIterator last)使用迭代器进行初始化构造

?代码测试示例:

#include 
#include 
using namespace std;

int main()
{
    vector<int> first;                                
    vector<int> second(4, 100);                       
    vector<int> third(second.begin(), second.end());  
    vector<int> fourth(third);

    int myints[] = { 16,2,77,29 };
    vector<int> fifth(myints, myints + sizeof(myints) / sizeof(int));

    cout << "The contents of fifth are:";
    for (vector<int>::iterator it = fifth.begin(); it != fifth.end(); ++it)
        cout << ' ' << *it;
    cout << '\n';

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

⌨️代码输出示例:

在这里插入图片描述

3.vector类对象的容量操作

在这里插入图片描述

在 vector 中同样对数组实现了容量操作,只不过相比 string ,删掉了 lenth 这类作用不大的函数

函数名功能说明
size返回数组有效数据个数
max_size返回的是 vector 理论上能够容纳的最大有效数据
resize将有效数据的个数增加或减少 n 个,多出的空间用默认值,少的截断即可
capacity返回空间总大小,即容量
reserve为数组增加预留空间,即增加预留容量
empty检测数组是否为空,是返回 true ,否则返回 false
shrink_to_fit请求 vector 对象将其容量缩小到和当前有效数据个数相匹配的大小

?值得注意的是:

  1. capacity 的代码在 vs 和 g++ 下分别运行会发现,vs 下 capacity 是按 1.5 倍增长的,g++ 是按 2 倍增长的。这个问题经常会考察,不要固化的认为,vector 增容都是 2 倍,具体增长多少是根据具体的需求定义的,vs 是 PJ 版本 STL ,g++ 是 SGI 版本 STL
  2. reserve 只负责开辟空间,如果确定知道需要用多少空间,reserve 可以缓解 vector 增容的代价缺陷问题
  3. resize 在开空间的同时还会进行初始化,影响 size

?代码测试示例:

#include 
#include 
using namespace std;

int main()
{
	vector<int> v(10, 1);
	cout << "size:" << v.size() << endl;
	cout << "max_size:" << v.max_size() << endl;

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

	v.reserve(20);
	cout << "reserve:" << v.capacity() << endl;
	cout << "capacity:" << v.capacity() << endl;
	cout << "empty:" << v.empty() << endl;

	v.shrink_to_fit();
	cout << "shrink_to_fit:" << v.capacity() << endl;

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

⌨️代码输出示例:

在这里插入图片描述

4.vector类对象的迭代器

在这里插入图片描述

vector 的迭代器和 string 的基本使用方法一致

函数名功能说明
begin + end迭代器:begin 获取开头一个数据 + end 获取最后一个数据下一个位置
rbegin + rend反向迭代器:rbegin 获取最后一个数据 + end 获取开头一个数据上一个位置
cbegin + cend和 begin + end 一样,但是常量迭代器只读
crbegin + crend和 rbegin + rend 一样,但是反向常量迭代器只读

?代码测试示例:

#include 
#include 
using namespace std;

int main()
{
	vector<int> v(10);
	for (size_t i = 0; i < 10; ++i)
	{
		v[i] = i;
	}

	cout << "迭代器:";
	vector<int>::iterator it1 = v.begin();
	while (it1 != v.end())
	{
		cout << *it1 << ' ';
		it1++;
	}
	cout << endl;

	cout << "反向迭代器:";
	vector<int>::reverse_iterator it2 = v.rbegin();
	while (it2 != v.rend())
	{
		cout << *it2 << ' ';
		it2++;
	}
	cout << endl;

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

⌨️代码输出示例:

在这里插入图片描述

5.vector类对象的元素修改

在这里插入图片描述

vector 相对于 string 增加了 emplace,去除了多余的 append

函数名功能说明
assign将新的内容赋值给数组
push_back数组尾插有效数据
pop_back数组尾删有效数据
insert在容器的指定位置插入元素
erase从容器里移除指定的元素或元素范围
swap交换两个 vector 对象的内容
clear移除 vector 对象中存储的所有字符
emplace在容器的指定位置直接构造元素
emplace_back在容器的末尾直接构造一个新元素

?代码测试示例:

#include 
#include 
using namespace std;

int main()
{
	vector<int> v{ 0,1,2,3,4,5,6,7,8,9 };

	cout << "v:";
	vector<int>::iterator it1 = v.begin();
	while (it1 != v.end())
	{
		cout << *it1 << ' ';
		it1++;
	}
	cout << endl;

	v.assign(10, 0);
	cout << "assign:";
	vector<int>::iterator it2 = v.begin();
	while (it2 != v.end())
	{
		cout << *it2 << ' ';
		it2++;
	}
	cout << endl;
	
	v.push_back(5);
	cout << "push_back:";
	vector<int>::iterator it3 = v.begin();
	while (it3 != v.end())
	{
		cout << *it3 << ' ';
		it3++;
	}
	cout << endl;

	v.pop_back();
	cout << "pop_back:";
	vector<int>::iterator it4 = v.begin();
	while (it4 != v.end())
	{
		cout << *it4 << ' ';
		it4++;
	}
	cout << endl;

	v.insert(v.begin() + 2, 3, 1);
	cout << "insert:";
	vector<int>::iterator it5 = v.begin();
	while (it5 != v.end())
	{
		cout << *it5 << ' ';
		it5++;
	}
	cout << endl;

	v.erase(v.begin() + 2, v.begin() + 5);
	cout << "erase:";
	vector<int>::iterator it6 = v.begin();
	while (it6 != v.end())
	{
		cout << *it6 << ' ';
		it6++;
	}
	cout << endl;

	vector<int> vv{ 1,3,4,5,6,7,8,9 };
	cout << "vv:";
	vector<int>::iterator it7 = vv.begin();
	while (it7 != vv.end())
	{
		cout << *it7 << ' ';
		it7++;
	}
	cout << endl;

	swap(v, vv);
	cout << "swap:" << ' ' << "v:";
	vector<int>::iterator it8 = v.begin();
	while (it8 != v.end())
	{
		cout << *it8 << ' ';
		it8++;
	}
	cout << ' ' << "vv:";
	vector<int>::iterator it9 = vv.begin();
	while (it9 != vv.end())
	{
		cout << *it9 << ' ';
		it9++;
	}
	cout << endl;

	v.emplace(v.begin(), 100);
	cout << "emplace:";
	vector<int>::iterator it10 = v.begin();
	while (it10 != v.end())
	{
		cout << *it10 << ' ';
		it10++;
	}
	cout << endl;

	v.emplace_back(100);
	cout << "emplace_back:";
	vector<int>::iterator it11 = v.begin();
	while (it11 != v.end())
	{
		cout << *it11 << ' ';
		it11++;
	}
	cout << endl;

	v.clear();
	cout << "clear:";
	vector<int>::iterator it12 = v.begin();
	while (it12 != v.end())
	{
		cout << *it12 << ' ';
		it12++;
	}
	cout << endl;

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126

⌨️代码输出示例:

在这里插入图片描述

6.vector类对象的元素访问

在这里插入图片描述

vector的本质是数组,固然是要[]运算符重载

函数名功能说明
operator[ ]像数组一样,使用方括号语法来访问其内部数据
at访问指定位置元素
back返回容器中最后一个元素的引用
front返回容器中第一个元素的引用

?代码测试示例:

#include 
#include 
using namespace std;

int main()
{
	vector<int> v{ 0,1,2,3,4,5,6,7,8,9 };

	cout << "operator[ ]:" << v[5] << endl;
	cout << "at:" << v.at(5) << endl;
	cout << "back:" << (v.back() = 10) << ' ' << "v:";
	vector<int>::iterator it1 = v.begin();
	while (it1 != v.end())
	{
		cout << *it1 << ' ';
		++it1;
	}
	cout << endl;

	cout << "front:" << (v.front() = -1) << ' ' << "v:";
	vector<int>::iterator it2 = v.begin();
	while (it2 != v.end())
	{
		cout << *it2 << ' ';
		++it2;
	}
	cout << endl;
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

⌨️代码输出示例:

在这里插入图片描述

7.vector迭代器失效问题

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector 的迭代器就是原生态指针 T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)

?会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等

#include 
using namespace std;
#include 
int main()
{
 vector<int> v{1,2,3,4,5,6};
 
 auto it = v.begin();
 
 // 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容
 // v.resize(100, 8);
 
 // reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
 // v.reserve(100);
 
 // 插入元素期间,可能会引起扩容,而导致原空间被释放
 // v.insert(v.begin(), 0);
 // v.push_back(8);
 
 // 给vector重新赋值,可能会引起底层容量改变
 v.assign(100, 8);
 
 /*
 出错原因:以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉,
而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的
空间,而引起代码运行时崩溃。
 解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新
赋值即可。
 */
 while(it != v.end())
 {
 cout<< *it << " " ;
 ++it;
 }
 cout<<endl;
 return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

?指定位置元素的删除操作–erase

#include 
using namespace std;
#include 
int main()
{
	 int a[] = { 1, 2, 3, 4 };
	 vector<int> v(a, a + sizeof(a) / sizeof(int));
	 // 使用find查找3所在位置的iterator
	 vector<int>::iterator pos = find(v.begin(), v.end(), 3);
	 // 删除pos位置的数据,导致pos迭代器失效。
	 v.erase(pos);
	 cout << *pos << endl; // 此处会导致非法访问
	 return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

erase 删除 pos 位置元素后,pos 位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果 pos 刚好是最后一个元素,删完之后 pos 刚好是 end 的位置,而 end 位置是没有元素的,那么 pos 就失效了。因此删除 vector 中任意位置上元素时,vs 就认为该位置迭代器失效了

以下代码的功能是删除vector中所有的偶数,请问那个代码是正确的,为什么?

#include 
using namespace std;
#include 
int main()
{
	vector<int> v{ 1, 2, 3, 4 };
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
			v.erase(it);
		++it;
	}

	return 0;
}
int main()
{
	vector<int> v{ 1, 2, 3, 4 };
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
			it = v.erase(it);
		else
			++it;
	}
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

显然第二个是正确的

使用 v.erase(it) 删除元素后,迭代器 it 会失效,v.erase(it) 删除元素时,erase 函数会返回一个指向被删除元素之后元素的迭代器,这意味着在调用 v.erase(it) 之后,it 不再指向原来的迭代器,所以需要将这个返回值赋给 it,可以保证 it 始终指向一个有效的元素,从而避免了迭代器失效的问题

?Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端

// 1. 扩容之后,迭代器已经失效了,程序虽然可以运行,但是运行结果已经不对了
int main()
{
	vector<int> v{ 1,2,3,4,5 };
	for (size_t i = 0; i < v.size(); ++i)
		cout << v[i] << " ";
	cout << endl;
	auto it = v.begin();
	cout << "扩容之前,vector的容量为: " << v.capacity() << endl;
	// 通过reserve将底层空间设置为100,目的是为了让vector的迭代器失效 
	v.reserve(100);
	cout << "扩容之后,vector的容量为: " << v.capacity() << endl;

	// 经过上述reserve之后,it迭代器肯定会失效,在vs下程序就直接崩溃了,但是linux下不会
	// 虽然可能运行,但是输出的结果是不对的
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}
程序输出:
1 2 3 4 5
扩容之前,vector的容量为: 5
扩容之后,vector的容量为 : 100
0 2 3 4 5 409 1 2 3 4 5
// 2. erase删除任意位置代码后,linux下迭代器并没有失效
// 因为空间还是原来的空间,后序元素往前搬移了,it的位置还是有效的
#include 
#include 
int main()
{
	vector<int> v{ 1,2,3,4,5 };
	vector<int>::iterator it = find(v.begin(), v.end(), 3);
	v.erase(it);
	cout << *it << endl;
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}
程序可以正常运行,并打印:
4
4 5

// 3: erase删除的迭代器如果是最后一个元素,删除之后it已经超过end
// 此时迭代器是无效的,++it导致程序崩溃
int main()
{
	vector<int> v{ 1,2,3,4,5 };
	// vector v{1,2,3,4,5,6};
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
			v.erase(it);
		++it;
	}
	for (auto e : v)
		cout << e << " ";
	cout << endl;
	return 0;
}
========================================================
// 使用第一组数据时,程序可以运行
[sly@VM - 0 - 3 - centos 20220114]$ g++ testVector.cpp - std = c++11
[sly@VM - 0 - 3 - centos 20220114]$ . / a.out
1 3 5
======================================================== =
// 使用第二组数据时,程序最终会崩溃
[sly@VM - 0 - 3 - centos 20220114]$ vim testVector.cpp
[sly@VM - 0 - 3 - centos 20220114]$ g++ testVector.cpp - std = c++11
[sly@VM - 0 - 3 - centos 20220114]$ . / a.out
Segmentation fault
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79

从上述三个例子中可以看到:SGI STL中,迭代器失效后,代码并不一定会崩溃,但是运行结果肯定不对,如果 it 不在 begin 和 end 范围内,肯定会崩溃的

与vector类似,string在插入+扩容操作+erase之后,迭代器也会失效

#include 
void TestString()
{
	string s("hello");
	auto it = s.begin();
	// 放开之后代码会崩溃,因为resize到20会string会进行扩容
	// 扩容之后,it指向之前旧空间已经被释放了,该迭代器就失效了
	// 后序打印时,再访问it指向的空间程序就会崩溃
	//s.resize(20, '!');
	while (it != s.end())
	{
		cout << *it;
		++it;
	}
	cout << endl;
	it = s.begin();
	while (it != s.end())
	{
		it = s.erase(it);
		// 按照下面方式写,运行时程序会崩溃,因为erase(it)之后
		// it位置的迭代器就失效了
		// s.erase(it); 
		++it;
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

总结: 在使用新的 iterator 类型的变量前,对迭代器重新赋值即可


希望读者们多多三连支持

小编会继续更新

你们的鼓励就是我前进的动力!

请添加图片描述

欢迎交流~长期互三互推交流可加v
微信名片
注:本文转载自blog.csdn.net的DARLING Zero two♡的文章"https://blog.csdn.net/Zero_VPN/article/details/145672860"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

106
编程语言
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top