2.4 vector元素访问函数
2.4.1 operator[]
reference operator[] (size_type n);
const_reference operator[] (size_type n) const;
std::vector 提供了两个重载的 operator[],用于通过索引访问容器中的元素:
reference operator[] (size_type n)
: 这个重载用于访问 std::vector 中指定索引位置 n 处的元素,并返回该元素的引用。通过此重载,可以对元素进行读取和修改操作。如果 n 超出了有效的索引范围(即小于 0 或大于等于 size()),行为将是未定义的(Undefined Behavior)。
const_reference operator[] (size_type n) const;
: 这个重载是 const 成员函数,用于在 const 修饰的 std::vector 对象中访问指定索引位置 n 处的元素,并返回该元素的常量引用。通过此重载,只能对元素进行读取操作,不能修改元素的值。
下面是一个使用 operator[] 的示例:
#include <iostream> #include <vector> int main() { std::vector<int> myVector = {1, 2, 3, 4, 5}; // 修改元素 myVector[2] = 10; // 将索引 2 处的元素修改为 10 // 读取元素 std::cout << "Element at index 2: " << myVector[2] << std::endl; // 使用 const_reference 进行读取(适用于 const std::vector) const std::vector<int>& constVector = myVector; std::cout << "Const element at index 4: " << constVector[4] << std::endl; return 0; }
输出结果:
Element at index 2: 10 Const element at index 4: 5
需要注意的是,通过 operator[] 访问元素时,应该确保索引在有效的范围内,否则可能导致未定义的行为。通常情况下,应该在访问元素之前使用 size() 函数检查索引的有效性。
2.4.2 at
reference at (size_type n);
const_reference at (size_type n) const;
std::vector 提供了两个重载的 at 函数,用于通过索引访问容器中的元素:
reference at (size_type n)
: 这个重载用于访问 std::vector 中指定索引位置 n 处的元素,并返回该元素的引用。通过此重载,可以对元素进行读取和修改操作。如果 n 超出了有效的索引范围(即小于 0 或大于等于 size()),at 函数会抛出 std::out_of_range 异常,以保证程序的健壮性。
const_reference at (size_type n) const;
: 这个重载是 const 成员函数,用于在 const 修饰的 std::vector 对象中访问指定索引位置 n 处的元素,并返回该元素的常量引用。通过此重载,只能对元素进行读取操作,不能修改元素的值。
下面是一个使用 at 函数的示例:
#include <iostream> #include <vector> int main() { std::vector<int> myVector = {1, 2, 3, 4, 5}; // 修改元素 myVector.at(2) = 10; // 将索引 2 处的元素修改为 10 // 读取元素 std::cout << "Element at index 3: " << myVector.at(3) << std::endl; // 使用 const_reference 进行读取(适用于 const std::vector) const std::vector<int>& constVector = myVector; std::cout << "Const element at index 4: " << constVector.at(4) << std::endl; // 尝试访问越界的索引(会抛出 std::out_of_range 异常) try { int value = myVector.at(10); } catch (const std::out_of_range& e) { std::cout << "Caught exception: " << e.what() << std::endl; } return 0; }
输出结果:
Element at index 3: 4 Const element at index 4: 5 Caught exception: vector::_M_range_check: __n (which is 10) >= this->size() (which is 5)
可以看到,在尝试访问越界的索引时,at 函数抛出了 std::out_of_range 异常,提醒我们访问索引越界了。因此,相比于 operator[],使用 at 函数可以更加安全地访问元素,特别是在不确定索引是否有效时。
2.4.3 front()
reference front();
const_reference front() const;
std::vector 提供了两个重载的 front 函数,用于访问容器中的第一个元素:
reference front()
: 这个重载用于访问 std::vector 容器中的第一个元素,并返回该元素的引用。通过此重载,可以对元素进行读取和修改操作。
const_reference front() const;
: 这个重载是 const 成员函数,用于在 const 修饰的 std::vector 对象中访问第一个元素,并返回该元素的常量引用。通过此重载,只能对元素进行读取操作,不能修改元素的值。
下面是一个使用 front 函数的示例:
#include <iostream> #include <vector> int main() { std::vector<int> myVector = {1, 2, 3, 4, 5}; // 修改第一个元素 myVector.front() = 10; // 将第一个元素修改为 10 // 读取第一个元素 std::cout << "First element: " << myVector.front() << std::endl; // 使用 const_reference 进行读取(适用于 const std::vector) const std::vector<int>& constVector = myVector; std::cout << "Const first element: " << constVector.front() << std::endl; return 0; }
输出结果:
First element: 10 Const first element: 10
可以看到,front 函数用于获取 std::vector 容器中的第一个元素,并且可以通过不同的重载版本来实现读取和修改操作,适用于常规和 const 对象。
2.4.4 back()
reference back();
const_reference back() const;
std::vector 提供了两个重载的 back 函数,用于访问容器中的最后一个元素:
reference back()
: 这个重载用于访问 std::vector 容器中的最后一个元素,并返回该元素的引用。通过此重载,可以对元素进行读取和修改操作。
const_reference back() const;
: 这个重载是 const 成员函数,用于在 const 修饰的 std::vector 对象中访问最后一个元素,并返回该元素的常量引用。通过此重载,只能对元素进行读取操作,不能修改元素的值。
下面是一个使用 back 函数的示例:
#include <iostream> #include <vector> int main() { std::vector<int> myVector = {1, 2, 3, 4, 5}; // 修改最后一个元素 myVector.back() = 10; // 将最后一个元素修改为 10 // 读取最后一个元素 std::cout << "Last element: " << myVector.back() << std::endl; // 使用 const_reference 进行读取(适用于 const std::vector) const std::vector<int>& constVector = myVector; std::cout << "Const last element: " << constVector.back() << std::endl; return 0; }
输出结果:
Last element: 10 Const last element: 10
可以看到,back 函数用于获取 std::vector 容器中的最后一个元素,并且可以通过不同的重载版本来实现读取和修改操作,适用于常规和 const 对象。
2.4.5 data() C++11
value_type* data() noexcept;
const value_type* data() const noexcept;
std::vector 提供了两个重载的 data 函数,用于访问底层存储数组的指针:
value_type* data() noexcept;
: 这个重载返回一个指向 std::vector 容器底层存储数组的非常量指针。通过此重载,可以对底层数组进行读写操作。noexcept 表示这个函数不会抛出异常。
const value_type* data() const noexcept;
: 这个重载是 const 成员函数,返回一个指向 std::vector 容器底层存储数组的常量指针。通过此重载,只能对底层数组进行读取操作,不能修改数组的内容。noexcept 表示这个函数不会抛出异常。
下面是一个使用 data 函数的示例:
#include <iostream> #include <vector> int main() { std::vector<int> myVector = {1, 2, 3, 4, 5}; // 获取底层存储数组的非常量指针 int* ptr = myVector.data(); // 修改底层存储数组 ptr[0] = 10; ptr[3] = 20; // 通过 const value_type* 获取底层存储数组的常量指针(适用于 const std::vector) const std::vector<int>& constVector = myVector; const int* constPtr = constVector.data(); // 读取底层存储数组 for (size_t i = 0; i < myVector.size(); ++i) { std::cout << "Element " << i << ": " << constPtr[i] << std::endl; } return 0; }
输出结果:
Element 0: 10 Element 1: 2 Element 2: 3 Element 3: 20 Element 4: 5
可以看到,data 函数用于获取 std::vector 容器底层存储数组的指针,并可以通过不同的重载版本来实现读取和修改操作,适用于常规和 const 对象。需要注意的是,在使用 data 函数时,需要确保 std::vector 不为空,否则获取到的指针可能是空指针。
2.5 vector增删查改函数
2.5.1 assign()
template <class InputIterator> void assign (InputIterator first, InputIterator last);
void assign (size_type n, const value_type& val);
std::vector 提供了两种重载的 assign 函数,用于替换 std::vector 中的内容:
template <class InputIterator> void assign (InputIterator first, InputIterator last);
:
- 这个重载模板函数接受一对迭代器 first 和 last 作为参数,它们表示一个范围。assign 函数将容器的内容替换为指定范围中的元素。
- 这个重载函数可以接受任何类型的迭代器,例如指针,普通迭代器或者 const 迭代器等。只要指定的范围是有效的,assign 函数就会将容器的内容替换为该范围内的元素。
void assign (size_type n, const value_type& val);
:
- 这个重载函数接受一个整数 n 和一个值 val 作为参数,将容器的内容替换为 n 个 val 值。
- n 表示新容器应该包含多少个 val 值,容器的大小将设置为 n,原来的元素都将被替换。
下面是使用 assign 函数的示例:
#include <iostream> #include <vector> int main() { std::vector<int> myVector; // 使用 assign 重载函数替换容器内容为一组元素 std::vector<int> sourceVector = {10, 20, 30, 40, 50}; myVector.assign(sourceVector.begin(), sourceVector.end()); // 打印容器内容 for (int num : myVector) { std::cout << num << " "; } std::cout << std::endl; // 使用 assign 重载函数替换容器内容为多个相同的元素 myVector.assign(5, 100); // 打印容器内容 for (int num : myVector) { std::cout << num << " "; } std::cout << std::endl; return 0; }
输出结果:
10 20 30 40 50 100 100 100 100 100
在这个示例中,我们首先使用 assign 函数将 myVector 容器的内容替换为另一个向量 sourceVector 中的元素。然后,我们再次使用 assign 函数将 myVector 的内容替换为 5 个值为 100 的元素。
2.5.2 push_back()
void push_back (const value_type& val);
std::vector 的 push_back 函数用于向容器的末尾添加一个新元素。它接受一个引用参数 const value_type& val,表示要添加的新元素的值。
其中,value_type 是 std::vector 中元素的类型,通常是模板参数中指定的类型。const 表示在函数中不会修改传入的值。
下面是一个使用 push_back 函数的示例:
#include <iostream> #include <vector> int main() { std::vector<int> myVector; myVector.push_back(10); myVector.push_back(20); myVector.push_back(30); // 打印容器内容 for (int num : myVector) { std::cout << num << " "; } std::cout << std::endl; return 0; }
输出结果:
10 20 30
2.5.3 pop_back()
void pop_back();
std::vector 的 pop_back 函数用于从容器的末尾移除一个元素。它没有参数,直接从容器的末尾删除最后一个元素。
下面是一个使用 pop_back 函数的示例:
#include <iostream> #include <vector> int main() { std::vector<int> myVector = {10, 20, 30}; std::cout << "Before pop_back: "; for (int num : myVector) { std::cout << num << " "; } std::cout << std::endl; myVector.pop_back(); std::cout << "After pop_back: "; for (int num : myVector) { std::cout << num << " "; } std::cout << std::endl; return 0; }
输出结果:
Before pop_back: 10 20 30 After pop_back: 10 20
在这个示例中,我们创建了一个 std::vector 容器,并初始化了三个元素:10、20 和 30。然后,我们使用 pop_back 函数将最后一个元素 30 从容器中移除。
2.5.4 insert()
iterator insert (iterator position, const value_type& val);
void insert (iterator position, size_type n, const value_type& val);
template <class InputIterator> void insert (iterator position, InputIterator first, InputIterator last);
std::vector 的 insert 函数用于在指定位置插入一个或多个元素。
1. 插入单个元素:
这个版本的 insert 函数在 position 位置之前插入一个元素,并返回指向插入元素的迭代器。
示例:
#include <iostream> #include <vector> int main() { std::vector<int> myVector = {1, 2, 3}; std::vector<int>::iterator it = myVector.begin() + 1; myVector.insert(it, 100); for (int num : myVector) { std::cout << num << " "; } std::cout << std::endl; return 0; }
输出结果:
1 100 2 3
2. 插入多个相同元素:
这个版本的 insert 函数在 position 位置之前插入 n 个值为 val 的元素。
示例:
#include <iostream> #include <vector> int main() { std::vector<int> myVector = {1, 2, 3}; std::vector<int>::iterator it = myVector.begin() + 1; myVector.insert(it, 3, 100); for (int num : myVector) { std::cout << num << " "; } std::cout << std::endl; return 0; }
输出结果:
1 100 100 100 2 3
3. 插入范围内的元素:
这个版本的 insert 函数在 position 位置之前插入从 first 到 last 区间内的元素。
示例:
#include <iostream> #include <vector> int main() { std::vector<int> myVector = {1, 2, 3}; std::vector<int> toInsert = {100, 200}; std::vector<int>::iterator it = myVector.begin() + 1; myVector.insert(it, toInsert.begin(), toInsert.end()); for (int num : myVector) { std::cout << num << " "; } std::cout << std::endl; return 0; }
输出结果:
1 100 200 2 3
在所有的 insert 函数中,参数 position 是一个迭代器,用于指定要插入的位置。这些函数将在 position 之前插入元素,并调整容器中的其他元素。
2.5.5 erase()
iterator erase (iterator position);
iterator erase (iterator first, iterator last);
std::vector 的 erase 函数用于从容器中移除一个或一段元素。
1.移除单个元素:
这个版本的 erase 函数移除由 position 指向的元素,并返回指向下一个元素的迭代器。
示例:
#include <iostream> #include <vector> int main() { std::vector<int> myVector = {1, 2, 3, 4, 5}; std::vector<int>::iterator it = myVector.begin() + 2; myVector.erase(it); for (int num : myVector) { std::cout << num << " "; } std::cout << std::endl; return 0; }
输出结果:
1 2 4 5
2. 移除一段元素
这个版本的 erase 函数移除从 first 到 last 区间内的元素,并返回指向下一个元素的迭代器。
示例:
#include <iostream> #include <vector> int main() { std::vector<int> myVector = {1, 2, 3, 4, 5}; std::vector<int>::iterator it1 = myVector.begin() + 1; std::vector<int>::iterator it2 = myVector.begin() + 3; myVector.erase(it1, it2); for (int num : myVector) { std::cout << num << " "; } std::cout << std::endl; return 0; }
输出结果:
1 4 5
在所有的 erase 函数中,参数可以是迭代器或者是一个表示范围的迭代器对,用于指定要移除的元素的位置。这些函数将移除指定的元素,并调整容器中的其他元素。移除后,迭代器失效,需小心使用。
2.5.6 swap()
void swap (vector& x);
std::vector 的 swap 函数用于交换两个顺序表的内容。它将当前顺序表和参数顺序表 x 的内容进行交换。
示例:
#include <iostream> #include <vector> int main() { std::vector<int> vec1 = {1, 2, 3}; std::vector<int> vec2 = {4, 5, 6}; std::cout << "Before swap:" << std::endl; for (int num : vec1) { std::cout << num << " "; } std::cout << std::endl; for (int num : vec2) { std::cout << num << " "; } std::cout << std::endl; vec1.swap(vec2); std::cout << "After swap:" << std::endl; for (int num : vec1) { std::cout << num << " "; } std::cout << std::endl; for (int num : vec2) { std::cout << num << " "; } std::cout << std::endl; return 0; }
输出结果:
Before swap: 1 2 3 4 5 6 After swap: 4 5 6 1 2 3
注意:swap 函数交换的是容器的内容,并不交换容器的容量大小,所以在交换后,两个容器的容量仍然保持不变。这可能导致在交换后,某个容器的容量比实际元素个数多,从而导致额外的内存分配和浪费。如果你想要交换两个容器的容量大小,可以使用 std::vector 的移动构造函数和移动赋值运算符。
2.5.7 clear()
void clear();
clear 函数用于清空 std::vector 中的所有元素,使其变为空顺序表。
示例:
#include <iostream> #include <vector> int main() { std::vector<int> vec = {1, 2, 3, 4, 5}; std::cout << "Before clear:" << std::endl; for (int num : vec) { std::cout << num << " "; } std::cout << std::endl; vec.clear(); std::cout << "After clear:" << std::endl; for (int num : vec) { std::cout << num << " "; } std::cout << std::endl; return 0; }
输出结果:
Before clear: 1 2 3 4 5 After clear:
注意:clear 函数只清空容器中的元素,并不改变容器的容量大小。如果你想要同时清空元素并释放内存,可以使用 std::vector 的 shrink_to_fit 函数。
2.5.8 emplace() C++11
template <class... Args> iterator emplace (const_iterator position, Args&&... args);
emplace 函数是 std::vector 的一个成员函数,用于在指定位置插入一个新元素,并通过传递参数来构造该元素。它支持在容器中的指定位置就地构造元素,而不需要额外的拷贝或移动操作。
参数说明:
position: 指定插入位置的迭代器,表示新元素将插入到该位置之前。
args: 可变参数模板,表示新元素的构造参数。
返回值:
返回一个迭代器,指向插入的新元素。
示例:
#include <iostream> #include <vector> class Person { public: Person(const std::string& name, int age) : name_(name), age_(age) { std::cout << "Constructing " << name_ << ", Age: " << age_ << std::endl; } private: std::string name_; int age_; }; int main() { std::vector<Person> people; // 使用 emplace 在位置 0 处插入新元素 people.emplace(people.begin(), "zhangsan", 25); people.emplace(people.begin() + 1, "lisi", 30); return 0; }
输出结果:
Constructing zhangsan, Age: 25 Constructing lisi, Age: 30
在上述示例中,emplace 函数会在指定位置就地构造 Person 类的对象,并通过传递参数 “zhangsan” 和 25 构造了一个名为 zhangsan,年龄为 25 的 Person 对象。然后又在位置 1 处就地构造了一个名为 lisi,年龄为 30 的 Person 对象。由于 emplace 避免了拷贝或移动操作,因此效率较高。
2.5.9 emplace_back() C++11
template <class... Args> void emplace_back (Args&&... args);
emplace_back 是 std::vector 的一个成员函数,用于在容器的末尾就地构造一个新元素,并通过传递参数来构造该元素。
示例:
#include <iostream> #include <vector> class Person { public: Person(const std::string& name, int age) : name_(name), age_(age) { std::cout << "Constructing " << name_ << ", Age: " << age_ << std::endl; } private: std::string name_; int age_; }; int main() { std::vector<Person> people; // 使用 emplace_back 在容器末尾插入新元素 people.emplace_back("zhangsan", 25); people.emplace_back("lisi", 30); return 0; }
输出结果:
Constructing zhangsan, Age: 25 Constructing lisi, Age: 30
在上述示例中,emplace_back 函数会在 people 容器的末尾就地构造 Person 类的对象,并通过传递参数构造了一个名为 zhangsan,年龄为 25 的 Person 对象,然后又构造了一个名为 lisi,年龄为 30 的 Person 对象。由于 emplace_back 避免了拷贝或移动操作,因此效率较高。
2.5.10 vector 迭代器失效问题
迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。
对于vector可能会导致其迭代器失效的操作有:
1. 会引起其底层空间改变的操作,都有可能是迭代器失效,比如: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(); v.assign(100, 8); while (it != v.end()) { cout << *it << " "; ++it; } cout << endl; return 0; }
出错原因:
以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉,而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的空间,而引起代码运行时崩溃。
解决方式:
在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新赋值即可。
2. 指定位置元素的删除操作–erase
#include <iostream> using namespace std; #include <vector> 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; }
erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效了。
3.删除vector中所有的偶数
迭代器失效:
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; }
4.Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端。
//扩容之后,迭代器已经失效了,程序虽然可以运行,但是运行结果已经不对了 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
//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; }
程序可以正常运行,并打印:
4 4 5
// 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; }
使用第一组数据时,程序可以运行
[kingxzq@localhost]$ g++ testVector.cpp - std = c++11 [kingxzq@localhost]$ . / a.out 1 3 5
使用第二组数据时,程序最终会崩溃
[kingxzq@localhost]$ vim testVector.cpp [kingxzq@localhost]$ g++ testVector.cpp - std = c++11 [kingxzq@localhost]$ . / a.out Segmentation fault
从上述三个例子中可以看到:SGI STL中,迭代器失效后,代码并不一定会崩溃,但是运行结果肯定不对,如果it不在begin和end范围内,肯定会崩溃的。
5. 与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; } }
迭代器失效解决办法:在使用前,对迭代器重新赋值即可。
结语
有兴趣的小伙伴可以关注作者,如果觉得内容不错,请给个一键三连吧,蟹蟹你哟!!!
制作不易,如有不正之处敬请指出
感谢大家的来访,UU们的观看是我坚持下去的动力
在时间的催化剂下,让我们彼此都成为更优秀的人吧!!!