Vector(1)
Vector是表示大小可以改变的数组的序列容器。(其实就是一个顺序表)
Vector是一个标准的模版,第一个模版参数是它要存什么数据类型,第二个参数可以先不关心。
构造函数:
析构函数:
它会自动调用,我们无需去管。
赋值:
遍历
它的遍历同样有3种方式:
第一种是下标+方括号;第二种是迭代器;
for (size_t i = 0; i < v3.size(); i++)
{
cout << v3[i] << " ";
}
cout << endl;
vector<int>::iterator it = v3.begin();
while (it != v3.end())
{
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
vector的默认扩容机制
//vector的默认扩容机制
void TestVectorExpand()
{
vector<int> v;
size_t sz;
sz = v.capacity();
cout << "capacity changed: " << sz << '\n';
cout << "making v grow:\n";
for (int i = 0; i < 100; ++i)
{
v.push_back(i);
if (sz != v.capacity())
{
sz = v.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
同样的,我们还是可以采用提前reserve的方式,减少扩容次数。
同样的,我们可以看到,我们的n给了多少,并不代表开的空间就是多少,可能会多开。
同时可以看出,当给的n比capacity要小,vector选择了不缩容,这一点和string没有保持一致,为什么?当然,是历史原因。
缩容
void test_vector2()
{
vector<int> v(10, 1);
v.reserve(20);
cout << v.size() << endl;
cout << v.capacity() << endl;
v.reserve(15);
cout << v.size() << endl;
cout << v.capacity() << endl;
v.reserve(5);
cout << v.size() << endl;
cout << v.capacity() << endl;
}
int main()
{
test_vector2();
return 0;
}
可以看到没有缩容,capacity没有任何变化。
在Linux也没有缩容,符合了文档。而string是会缩容的。
缩容占不到什么便宜,我们要注意:缩容并不是我们想象中从一块空间的中间某个部分想释放就释放,而是要从哪申请就从哪释放。
resize
我们看一下这三种不同的情况:
后两者归为一类,前者为一类。第一种情况一般不会缩容,但是会删除数据。给5就是只保留前5个数据;大于size就是要插入数据,空间不够就扩容;
member type就是在类里面typedef的甚至是内部类的类型。所以我们可以看到value_type就是第一个模版参数,也就是T。
所以对于resize来说,如果给定了值就用这个值补到n,没有给定就用T类型的缺省值。就是默认构造构造的值。
如果n大于capacity,就会扩容。
void test_vector3()
{
vector<int> v(10, 1);
v.reserve(20);
cout << v.size() << endl;
cout << v.capacity() << endl;
v.resize(15,2);
cout << v.size() << endl;
cout << v.capacity() << endl;
v.resize(25, 3);
cout << v.size() << endl;
cout << v.capacity() << endl;
v.resize(5);
cout << v.size() << endl;
cout << v.capacity() << endl;
}
int main()
{
test_vector3();
return 0;
}
operator[]和at
我们之前就说过,operator[]会越界断言,而at是越界会抛异常。
front和back
就是取首和尾的数据。
data
是底层的那个指针,但我们一般不会返回data。
assign
assign是一种赋值,但是一般我们都用operator[]赋值。这个不常用。
assign可以用n个value赋值,或者用迭代区间赋值。
push_back和pop_back
可以看到支持尾插和尾删,和string一样,没有直接支持头插和头删。
想头插或头删就得用insert或者erase。
insert和erase
这个insert是比之前的string的insert简洁了很多的。但这里不支持下标了,只支持迭代器。(是为了兼容,毕竟在链表这样的数据结构中,是没有下标的)
void test_vector4()
{
vector<int> v(10, 1);
v.push_back(2);
v.insert(v.begin(), 0);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
v.insert(v.begin() + 3, 10);//在第3个位置之前插入10
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
test_vector4();
return 0;
}
当然,insert和erase都要挪动数据,效率不高。
swap
为什么要实现swap,和string的理由一样,怕交换的时候效率低。
clear
和之前一样,清掉所有的数据。
可以看出,vector相比string简洁了很多。且和string有很多相似性,本质都是顺序表或者说数组。
emplace系列我们暂时不去看。
然后,也有一个全局的swap,和string一样。
vector支持比较大小。
实现的是小于和等于,其他的进行了复用。
一般我们用不上vector的比较大小。
vector不支持流插入和流提取。
为什么?vector的输入输出是具有很多不确定性的。
自己要实现其实很方便。
int x;
cin >> x;
v.push_back(x);
vector<int>v1(5, 0);
for (size_t i = 0; i < 5; i++)
{
cin >> v1[i];//方括号返回的是引用
}
for (auto e:v1)
{
cout << e << ",";
}
cout << endl;
思考:vector能够替代string吗?
底层都是char*的指针。
但是是不行的。
其中底层的一个重大区别在于’\0’。string的’\0’很好兼容了C语言。除此之外还有很多的问题,总之专门将string划分出来是很有意义的。字符数组用得很多,且有很多专用的需求。比如还有编码的需求。
vector存放string类型
void test_vector5()
{
vector<string> v1;
string s1("xxxx");
v1.push_back(s1);
v1.push_back("yyyyy");//const char*可以隐式类型转换成string
}
这样隐式类型转换后用起来更方便简洁。
然后我们需要注意的是:
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
在vector中存放的类型为string时,需要谨慎,因为范围for是逐次去拷贝v1中的值给e,这样拷贝的代价是比较大的。而且拷贝构造还要深拷贝,要开空间然后再拷贝数据。
所以在使用范围for时我们加引用,如果不改变原来的值,就把const也加上。
for (const auto& e : v1)
{
cout << e << " ";
}
cout << endl;
vector里面可以存vector
//二维数组 10*5
vector<int> v(5, 1);
vector<vector<int>> vv(10,v);
本文到此结束,敬请期待后文。