C++中的vector
一丶vector的介绍和使用
1.vector的简单介绍
关于vector:
1.vector是表示可变大小数组的容器。
2.就像数组一样,vector也采用连续存储空间来存储元素。这意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
3.本质上讲,vector使用动态分配数组来存储它的元素。当新元素插入时,如果空间不够,这个数组需要被重新分配大小,也就是需要增加存储空间。其做法是,分配一个新的数组,然后将全部元素移动到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每一都重新分配大小,这取决于vector是否需要扩容。
4.vector分配空间的策略:vector会分配一些额外的空间以适应可能新的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
5.因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
6.与其他动态序列容器相比,比方说deque丶list丶forward_list等等,vector在访问元素时要更加高效,在末尾添加和删除元素相对高效。对于其他不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好。
2.vector的使用
I.构造函数
(constructor)构造函数声明
1. vector()
无参构造
2. vector(size_type n, const value_type& val = value_type())
构造并初始化n个val
3. vector (const vector& x)
拷贝构造
4. vector (InputIterator first, InputIterator last)
使用迭代器进行初始化构造
II.相关的迭代器
iterator的使用
1. begin+end
获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator
2. rbegin+rend
rbegin()获取最后一个数据位置的reverse_iterator,rend()获取第一个数据前一个位置的
reverse_iterator
III.空间容量以及大小
容量空间
1. size
获取数据个数
2. capacity
获取容量大小
3. empty
判断是否为空
4. resize
改变vector的size
5. reverse
改变vector的capacity
-
capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍,g++是按2倍增长的。注意:不要固化地认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。
-
reserve只负责开辟空间,它不会修改vector的成员_size, 如果确定需要多少空间,reverse可以缓解vector增容的代价缺陷问题,这个会在后面的代码体现。
-
resize在开空间的同时还会进行初始化,会修改vector的成员_size。
// 如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够
// 就可以避免边插入边扩容导致效率低下的问题了
void TestVectorExpandOP()
{
vector<int> v;
size_t sz = v.capacity();
v.reserve(100); // 提前将容量设置好,可以避免一遍插入一遍扩容
cout << "making bar 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';
}
}
}
IV. vector的增删查改
vector的增删查改
1. push_back
尾插
2. pop_back
尾删
3. find
查找.(注意:这个是算法模块实现,不是vector的成员接口)
4. insert
在position位置之前插入val
5. erase
删除position位置的数据
6.swap
交换两个vector的数据空间
7. operator[]
像数组一样访问
3.迭代器失效的问题
迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装.比如:vector的迭代器就是原生态指针T*.
因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能崩溃.)。
对于vector可能会导致其迭代器失效的操作有:
- 会引起其底层空间改变的操作,都有可能是迭代器失效。比如:resize丶reserve丶insert丶assign丶push_back等等。
include <iostream>
using namespace std;
#include <vector>
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重新
赋值即可。
*/
//鉴于assign操作有可能会修改底层空间,导致it指向的可能不是assign后的v.begin
//所以要重新获取一下
it = v.begin()
while (it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
return 0;
}
- 指定位置元素的删除操作–erase
void test_vector4()
{
using namespace std;
std::vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
std::vector<int>::iterator it = v1.begin();
while (it != v1.end())
{
if (*it % 2 == 0)
{
//v1.erase(it);
//注意:这里的写法是错误的 因为it如果进行erase后
// 不允许在进行访问 VS编译器进行了强制的检查
// 但是在while处又进行it的访问 这很明显会报错
//正确的写法:
it = v1.erase(it);
//要更新it 在erase后 it指向的是删除元素的下一个位置
//同理 Linux下不会强制让迭代器失效
}
else
{
++it;
}
}
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
}
- Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端。
具体是这样的,比方说我们进行erase操作后,迭代器在未更新之前,在VS环境下是不能访问的,否则会报错。而在Linux环境下,即使迭代器未更新,仍让可以允许访问,不会报错。但是,实际上我们知道这个指向已经不对了,它实际指向的内容不是我们想要的。Linux只是允许我们访问这个空间了,但实际上这个空间的指向不是我们想要的。
// 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 <vector>
#include <algorithm>
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;
}
// 3: erase删除的迭代器如果是最后一个元素,删除之后it已经超过end
// 此时迭代器是无效的,++it导致程序崩溃
int main()
{
vector<int> v{ 1,2,3,4,5 };
// vector<int> 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;
}
//Linux下去执行相同的代码:
========================================================
// 使用第一组数据时,程序可以运行
[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
从上述三个例子中可以看到:SGI STL中,迭代器失效后,代码并不一定会崩溃,但是运行结果肯定不对,如果it不在begin和end范围内,肯定会崩溃的。
- 与vector类似,string在插入+扩容操作+erase之后,迭代器也会失效
#include <string>
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;
}
}
迭代器失效解决办法:在使用前,对迭代器重新赋值即可。
二丶vector的深度剖析和模拟实现
1. 使用memcpy拷贝出现的问题
假设模拟实现的vector中的reserve接口中,使用memcpy进行拷贝,会发生什么呢?
int main()
{
bite::vector<bite::string> v;
v.push_back("1111");
v.push_back("2222");
v.push_back("3333");
return 0;
}
阐述一下memcpy的特性:
1. memcpy是内存的二进制格式拷贝,将一段内存空间中的内容原封不动的拷贝到另外一段空间中。
2. 如果拷贝的是内置类型的元素,memcpy即高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,那么就会出错,这是因为memcpy的拷贝实际上是浅拷贝。
既然是浅拷贝,那么涉及资源管理,就不能使用memcpy了,否则会造成内存泄漏甚至程序崩溃。
2. vector的模拟实现
鉴于模板声明和定义分离时,会出现链接问题,因此我们将声明和定义都放在vector.h文件中,并在vector.cpp中直接测试我们实现的功能。
vector.h
#pragma once
//模板的声明和定义要放到一个文件里
#include <assert.h>
#include <iostream>
#include <vector>
#include <list>
namespace bit
{
/*template<class T>
class vector
{
public:
T* _a;
size_t _size;
size_t _capcacity;
};*/
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
const_iterator cbegin() const
{
return _start;
}
const_iterator cend() const
{
return _finish;
}
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
//迭代器区间初始化(也是构造函数)
//为类模板的成员函数 同时该函数为函数模板
//其目的是支持任一容器的迭代器区间初始化
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
//n个value的构造参数
vector(size_t n, const T& val = T())
{ //const T& val = T()这种写法是提供一个缺省值
//该缺省值为匿名对象 (无论是内置类型还是自定义类型都可以)
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
vector(int n, const T& val = T())
{ //const T& val = T()这种写法是提供一个缺省值
//该缺省值为匿名对象 (无论是内置类型还是自定义类型都可以)
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
vector(initializer_list<T> il)
{
reserve(il.size());
for (auto e: il)
{
push_back(e);
}
}
//表示强制编译器生成一个默认构造函数
vector() = default;
//未自行实现任何的构造 那么编译器将提供
//一旦实现一种构造 那么编译器将不提供其他构造
vector(const vector<T>& v)
{
//直接开辟好内存空间 不必每几次的时候就开辟
//大大减少了扩容次数 减少了能耗
reserve(v.capacity());
for (auto e : v)
{
push_back(e);
}
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
vector<T>& operator=(vector<T>& v)
{
this->swap(v);
return *this;
}
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
}
void reserve(size_t n)
{
//if (n > capacity())
//{
// T* tmp = new T[n];
// if (_start)
// {
// memcpy(tmp, _start, sizeof(T) * size());
// delete[] _start;
// } //要注意 当代码进行到此处的时候 _start是有可能成为野指针的
// _start = tmp; //在此处_start才为正常的指针
// //这里+的是size() 因为是将原来的数据拷贝过来
// _finish = _start + size();
// //这里+的是n,而不是capacity()
// //因为n是扩容好的新空间长度
// _end_of_storage = _start + n;
// //上面通过size()计算的_finish是有问题的
// //因为出现了扩容
// //原本的_start是先被delete了 然后重新赋值
// //此时的_start已经不是原先的位置了
// //但是size()的计算是通过_finish - _start得到的
// //而此时_start已经被更改了 而_finish的位置还在原来的位置
// //这就导致了_finish的值计算错误
// //我们需要的是原先_start的位置或者是原vector的长度
// //因此需要先保存起来我们需要的数据
// 假设我这么写:
// //_finish = tmp + size();
// //_start = tmp;
// //_end_of_storage = _start + n;
// 这种写法是这样的:此时_start和_finish还为被修改
// 我们先求得_finish的值 求出只会就不会出现上面的情况
// 但是这个也有条件限制的 前提是_start没有被delete
// 否则_start就是野指针了 不能进行使用
// 而且上面的这种写法的顺序也不美观 因此不采用
// //我们采取先保存vector数组的长度的方法
// //在修改_start和_finish之前 先计算出长度 并保存起来
//}
//正确的写法:
if (n > capacity())
{
size_t oldsize = size(); //先保存原来数组的元素个数
T* tmp = new T[n];
if (_start)
{
//注意:memcpy是浅拷贝 其是按字节拷贝的
//memcpy(tmp, _start, sizeof(T) * size());
//实现深拷贝
for (size_t = 0; i < oldsize; i++)
{
tmp[i] = _start;
}
delete[] _start;
}
//此时tmp和_start是等效的
_start = tmp; //修改_start
_finish = tmp + oldsize; //修改_finish
_end_of_storage = _start + n;
}
}
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _end_of_storage - _start;
}
T& operator[](size_t i)
{
assert(i < size());
return _start[i];
}
const T& operator[](size_t i) const
{
assert(i < size());
return _start[i];
}
void push_back(const T& x)
{
if (_finish == _end_of_storage)
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
//reserve中已经完成了_finish和_end_of_storage的修改
}
*_finish = x;
++_finish;
}
void pop_back()
{
assert(size() > 0);
--_finish;
}
//但凡涉及扩容和迭代器使用时 都要注意迭代器失效的问题
void insert(iterator& pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish); //此处可以等于_finish 可以在尾端进行插入
if (_finish == _end_of_storage)
{
size_t len = pos - _start;
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
pos = _start + len;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = x;
++_finish;
}
//erase是否会迭代器器失效呢?
//迭代器失效的充分条件是扩容或缩容导致的原生迭代器成为野指针
//倘若erase的实现是通过缩容 那么erase操作必然会导致迭代器失效
//我们这里是通过指针移动和数据覆盖的方式实现erase的
//没有进行缩容 那么自然不存在迭代器失效的问题
//不过 在不同的环境下 erase的实现是不同的 是否导致迭代器失效
//要根据底层实现来进行判断
void erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;
++it;
}
--_finish;
//使用标准库中的erase
//在执行erase操作后 不能再访问erase处的迭代器
//我们设定的标准是这样的:
//只要erase 迭代器就当作失效
//那么原erase处的迭代器 不允许进行访问
//注意:迭代器失效不一定会导致原迭代器成为野指针
// 迭代器为野指针只是迭代器失效的一种方式
}
//在系统的insert erase 其返回值是迭代器
//insert erase 想要访问原迭代器 可以通过接收返回值的方式
//来更新原迭代器 从而达到访问原迭代器的目的(即使原迭代器的实际地址已经更改)
private:
iterator _start = nullptr; //相当于_a
iterator _finish = nullptr; //相当于_a + _size
iterator _end_of_storage = nullptr; //相当于_a + _capacity
};
void test_vector1()
{
bit::vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
for (size_t i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
cout << endl;
//只有实现了对应的迭代器 才能使用范围for
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
vector<int>::iterator it = v1.begin(); //一种具有封装性丶广泛性的通用写法
//它等价于:int* v1.begin()
//通过typeid我们可以查看一下
cout << typeid(it).name() << endl;
while (it != v1.end())
{
cout << *it << " ";
++it;
}
cout << endl;
//不过些许风霜罢了
}
void test_vector2()
{
using namespace std;
bit::vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
/*for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
v1.insert(v1.begin(), 0);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
v1.erase(v1.begin());
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;*/
//对于push_front pop_front这种操作要谨慎使用
//涉及数据的挪动 效率较低
/*v1.insert(v1.end(), 30);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;*/
int x;
cin >> x;
//没有x就不插入 有x则插入到x的前面
bit::vector<int>::iterator it = find(v1.begin(), v1.end(), x);
if (it != v1.end())
{
//此处在插入数据后 如果扩容 迭代器就会失效
//此时在VS编程环境下再访问对应的迭代器 会报错
//在不同的环境下 迭代器可能会失效 也可能不会
//不过我们把他统一当作会失效来处理 确保它的通用性
v1.insert(it, 1000);
//当insert在插入时扩容 原先的it迭代器会被delete掉
//而it作为形参是不给改变实参的 那么就会导致原先的it
//成为了野指针 那么再次访问it时 就会出现错误
cout << *it << endl;
//解决方法是什么呢? 修改成引用传入是否可以呢?
//修改为引用型 对于下面这种情况 是不行的
//v1.insert(v1.begin(), 0);//因为v1.begin返回是一个临时变量 具有常性
//那么它是不能传递给引用类型的 显然这种写法是不行的
//我们要寻找一个满足各种传递方式的写法
//比如将扩容后的地址返回给it 通过更新it 来达到目的
}
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
}
void test_vector3()
{
//using namespace std;
bit::vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
v1.erase(v1.begin());
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
int x;
cin >> x;
bit::vector<int>::iterator it = find(v1.begin(), v1.end(), x);
if (it != v1.end())
{
//在erase it以后 我们统一认为it失效
v1.erase(it);
//在Linux环境下 此处不会失效
//系统是这样设计的:在erase后 会给原迭代器设置一个标志
// 通过该标志来判断迭代器是否能进行访问
}
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
}
void test_vector4()
{
using namespace std;
std::vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
std::vector<int>::iterator it = v1.begin();
while (it != v1.end())
{
if (*it % 2 == 0)
{
//v1.erase(it);
//注意:这里的写法是错误的 因为it如果进行erase后
// 不允许在进行访问 VS编译器进行了强制的检查
// 但是在while处又进行it的访问 这很明显会报错
//正确的写法:
it = v1.erase(it);
//要更新it 在erase后 it指向的是删除元素的下一个位置
//同理 Linux下不会强制让迭代器失效
}
else
{
++it;
}
}
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
}
void test_vector6()
{
bit::vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
bit::vector<int> v2;
v2 = v1;
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
}
void test_vector7()
{
/*bit::vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
vector<int> v2(v1.begin() + 1, v1.end() - 1);
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;*/
using namespace std;
list<int> lt;
lt.push_back(100);
lt.push_back(200);
lt.push_back(300);
vector<int> v4(lt.begin(), lt.end());
for (auto e : v4)
{
cout << e << " ";
}
cout << endl;
}
void test_vector8()
{
int i = 0;
int j(1);
int k = int();
int x = int(2);
//C++的内置类型也有构造 这是为了满足模板的需求
//调用n个值的构造
vector<string> v1(10);
vector<string> v2(10, "xxx"); //存在隐式类型转换
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
vector<int> v3(10, 1); //想生成10个1的vector<int> 但是失败了
//想调用的是vector(size_t n, const T & val = T())
//实际上调用的是vector(InputIterator first, InputIterator last)
//因为第一个参数列是size_t, T;第二个参数列是 T, T
//那么明显第二个会很合适
//因为10个1是同种类型 都会被编译器识别为同一类型
//它报出的错误是:非法的间接寻址
//因为调用的是vector(InputIterator first, InputIterator last)
//在该构造内部会对形参first和last解引用
//由于我们传入的数据类型会被编译器解释为int
//但是int不能被*(解引用) 于是就出现了非法的间接寻址的错误
//解决方法:
//一种是给数据后面加相应后辍来表示数据类型 来让编译器直接识别
//vector<int> v3(10u, 1); 表示为unsigned int 即是size_t
//另一种方法是提供vector(int n, const T& val = T())
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
}
class A
{
public:
A(int a1 = 0)
:_a1(a1)
,_a2(0)
{}
A(int a1, int a2)
:_a1(a1)
, _a2(a2)
{}
private:
int _a1;
int _a2;
};
void test_vector9()
{
//以前学过 单参数和多参数的隐式类型转换
//单参数是对象名后面直接跟() 多参数是跟={}
//单参数隐式类型转换
A aa1(1);
A aa2 = 1;
A aa3 = { 1 }; //也可以这么写 但是不建议哦
//多参数隐式类型转换
A aa4(1,1);
A aa5 = {2,2};
A aa6{ 1,1 }; //可省略等号
const A& aa8 = { 1,1 };
//此处也是隐式类型转换 是参数列表初始化std::initializer_list
// std::initializer_list
//标准库中对应的原型:
// vector (initializer_list<value_type> il,
// const allocator_type& alloc = allocator_type());
std::vector<int> v1({ 1,2,3,4,5,6 }); //直接构造
std::vector<int> v2 = { 1,2,3 }; //隐式类型转换
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
auto il1 = { 1,2,3,4,5,6 };
//initializer_list内部存有两个指针_First和_Last
//分别指向开始和超尾
//其内部还有size() begin() end() 也就是说它支持范围for
initializer_list<int> il2 = { 1,2,3,4,5,6,7,8 };
for (auto e : il2)
{
cout << e << " ";
}
cout << endl;
cout << typeid(il1).name() << endl;
cout << sizeof(il2) << endl;
vector<A> v3 = { 1, A(1), A(2,2), A{1}, A{2,2},{1},{2,2} };
//分别对应:隐式 构造 构造 隐式 隐式...
}
void test_vector10()
{
vector<string> v1;
v1.push_back("11111111111111");
v1.push_back("11111111111111");
v1.push_back("11111111111111");
v1.push_back("11111111111111");
v1.push_back("11111111111111");
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
}
}
vector.cpp
#include <iostream>
#include <algorithm>
#include <string>
//注意:命名空间展开必须放在#include "vector.h"之上,否则在"vector.h"中将找不到std的声明而报错,这是因为在"vector.h"展开的说话std还未展开造成的。
using namespace std;
#include "vector.h"
int main()
{
//bit::test_vector1();
//bit::test_vector2();
//bit::test_vector3();
//bit::test_vector6();
//bit::test_vector7();
//bit::test_vector8();
//bit::test_vector8();
//bit::test_vector9();
bit::test_vector10();
return 0;
}
本博客仅供个人参考,如有错误请多多包含。
Aruinsches-C++日志-5/28/2024
标签:迭代,back,C++,v1,vector,push,size From: https://blog.csdn.net/m0_73968399/article/details/139194707