【C++进阶】深入STL之vector:构建高效C++程序的基石

简介: 【C++进阶】深入STL之vector:构建高效C++程序的基石

学习STL中的vector:开启C++容器之旅的前言

  • 在C++的编程世界中,标准模板库(STL)无疑是每位开发者都需要熟练掌握的工具集。其中,vector作为STL中最常用的动态数组容器之一,以其灵活、高效和易用的特性,成为了众多C++程序员的首选。

vector容器允许我们存储任意数量的同类型元素,并且能够根据需要进行动态扩展。这种灵活性使得vector在处理大量数据时变得尤为高效,无论是在科学计算、图形处理、网络编程还是游戏开发等领域,我们都能看到vector的身影。
现在让我们一起踏上学习STL中vector的旅程吧!


📒1.vector类的基本概念

vector是C++标准模板库(STL)中的一个动态数组容器,它提供了对一段连续空间的动态管理功能。与普通的C++数组相比,vector具有许多优点,如可以动态调整大小、支持随机访问等


vector类成员函数:

class string
{
private:
  iterator _start;
  iterator _finish;
  iterator _end_of_storage;
};


📕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); 使用迭代器进行初始化构造
int main()
{
  vector<int> v1; // 无参构造
  vector<int> v2(10, 0); // 构造并初始化n个val
  vector<int> v3(v2); // 拷贝构造
  vector<int> v4(v2.begin(),v2.end()); // 使用迭代器进行初始化构造
  return 0;
}

关于 vector iterator 的使用

iterator的使用 接口说明
begin +end 获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator
rbegin + rend 获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的reverse_iterator

其实vector的很多用法和string类似


🌞vector类对象的容量操作

容量空间 接口说明
size 获取数据个数
capacity 获取容量大小
empty 判断是否为空
resize 改变vector的size
reserve 改变vector的capacity
int main()
{
  vector<int> v(10, 0);
  cout << v.size() << endl; // 获取数据个数
  cout << v.capacity() << endl; // 获取容量大小

  v.reserve(20); // 改变vector的capacity
  cout << endl;
  cout << "after reserve size: " << v.size() << endl;
  cout << "after reserve capacity: " << v.capacity() << endl;
  
  cout << endl;
  
  v.resize(20); // 改变vector的size
  cout << "after resize size: " << v.size() << endl;
  cout << "after resize capacity: " << v.capacity() << endl;

  cout << v.empty() << endl; // 判断是否为空
  return 0;
}

注意:

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

🌙vector类对象的增删查改

vector增删查改 接口说明
push_back 尾插
pop_back 尾删
insert 在pos之前插入val
erase 删除pos位置的数据
swap 交换两个vector的数据空间
operator[ ] 像数组一样访问

注意:find 查找,这个是算法模块实现,不是vector的成员接口

代码示例:

int main()
{
  vector<int> v1(1, 0);
  v1.push_back(1); // 尾插
  v1.push_back(2);
  v1.push_back(3);
  v1.push_back(5); // 尾插后v1 : 0,1,2,3,5

  v1.pop_back(); // 尾删后v1 : 0,1,2,3

  cout << "v1: ";
  for (size_t i = 0; i < v1.size(); i++) // 遍历vector数组
  {
    cout << v1[i] << " "; // operator[]随机访问
  }
  cout << endl;

  vector<int> v2(10, 0);
  cout << "v2: ";
  for (size_t i = 0; i < v2.size(); i++)
  {
    cout << v2[i] << " ";
  }
  cout << endl;
  
  v1.swap(v2); // 交换v1,v2内容
  cout << "after swap v1: ";
  for (size_t i = 0; i < v1.size(); i++)
  {
    cout << v1[i] << " ";
  }
  cout << endl;

  vector<int>::iterator it = find(v1.begin(), v1.end(), 0); // 查找
  cout << *it;
  return 0;
}


注意:insert和erase在vector里面有点特殊,在vector上它使用的都是迭代器

erase往往和find搭配使用

vector<int> v{1,2,3,4,5,6,7,8,9};
auto pos = find(v.begin(),v.end(),6);
v.erase(pos);

//删除一个区间
v.erase(v.begin() + 1,v.end() - 1);

insert头插一个0

vector<int> v{1,2,3,4,5,6,7,8,9};
auto pos = v.begin();
v.insert(pos,0);

📜3. vector类的模拟实现

🍁vector的成员变量

首先我们要先搞清楚 vector的成员变量,我们清楚 vector类在底层实际上也是指针,在模拟实现 vector之前,我们创建一个属于自己的命名空间来与库里面的区分

namespace pxt
{
  template<class T>
  class vector
  {
  public:
    // vector的迭代器是一个原生指针
    typedef T* iterator;
    typedef const T* const_iterator;
    // 迭代器相关(迭代器主要就是找到头尾)
    iterator begin()
    {
      return _start;
    }
    iterator end()
    {
      return _finish;
    }
    const_iterator begin() const
    {
      return _start;
    }
    const_iterator end() const
    {
      return _finish;
    }
  private:
    // 成员变量
    iterator _start; // 指向有效数据的头
    iterator _finish; // 指向有效数据的尾
    iterator _end_of_storage; // 指向最大空间的地方
  };
}


🌸vector的构造函数

无参构造:

vector()
  :_start(nullptr)
  , _finish(nullptr)
  , _end_of_storage(nullptr)
{}

带参的构造函数

vector(size_t n, const T& val = T())
  :_start(nullptr)
  , _finish(nullptr)
  , _end_of_storage(nullptr)
{
  reserve(n); // 开辟空间
  for (size_t i = 0; i < n; i++)
  {
    push_back(val); // 给初始值赋值
    // reserve,push_back的模拟实现下面会讲
  }
}

迭代器区间构造

为了实现不同类型迭代器的构造,这里需要再创建一个模板

template <class InputIterator> // 类似与STL
vector(InputIterator first, InputIterator last)
  :_start(nullptr)
  , _finish(nullptr)
  , _end_of_storage(nullptr)
{
  while (first != last)
  {
    push_back(*first);
    ++first;
  }
}

🌷vector的析构函数

析构函数比较简单,将空间释放,各个指针置为空

~vector()
{
  delete[] _start;
  _start = _finish = _end_of_storage = nullptr;
}

🌻vector的拷贝构造函数

vector(const vector<T>& v)
  :_start(nullptr)
  , _finish(nullptr)
  , _endof_storage(nullptr)
  {
    reserve(v.capacity());
    for (const auto& e : v)
    {
      push_back(e);
    }
  }

🌼vector的运算符重载

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> tmp)
  {
    swap(tmp); 
    return *this;
  }

🍂vector容量相关函数

跟容量有关的函数size,capacity,empty,resize,reverse,push_back

size_t size() const
{
  return _finish - _start;
}

size_t capacity() const
{
  return _endof_storage - _start;
}

bool empty() const
{
  return _start == _finish;
}

reverse

reverse只会改变capacity的大小,并不会改变size的大小

void reserve(size_t n)
{
  if (n > capacity()) // n < capacity()时,则不需要作出反应
  {
    size_t sz = size(); // 先保存以下size的值
    T* tmp = new T[n]; // 开辟空间
    if (_start)
    {
      //memcpy(tmp, _start, sizeof(T)*sz);
      for (size_t i = 0; i < sz; ++i)
      {
        tmp[i] = _start[i];
      }
      delete[] _start;
    }

    _start = tmp;
    _finish = _start + sz;
    _end_of_storage = _start + n;
  }
}

resize

resize不仅会改变size大小,也会改变capacity大小

void resize(size_t n, const T& val = T()) // val=T()用了匿名对象
{
  if (n > capacity())
  {
    reserve(n); // 开辟额外空间
  } 

  if (n > size())
  {
    // 初始化填值
    while (_finish < _start + n)
    {
      *_finish = val;
      ++_finish;
    }
  }
  else
  {
    _finish = _start + n;
  }
}

注意:C++将内置类型特殊处理过,int/char等等都被升级为了类,所以可以使用int()表示匿名对象

int main()
{
  cout << int() << endl; // int的缺省值为0,所以输出 0
  return 0;
}

push_back

void push_back(const T& x)
{
  if(_finish == _end_of_storage)
  {
    size_t sz = size();
    size_t cp = capacity() == 0 ? 4 : capacity() * 2;
    T* tmp = new <T>(cp);
    if(_start)
    {
      // memcpy(tmp, _start, sizeof(T) * sz);
      for (size_t i = 0; i < sz; ++i)
      {
        tmp[i] = _start[i];
      }
      delete[] _start;
    }
    _start = tmp;
    _finish = _start + sz;
    _end_of_storage = _start + cp;
  }
  *_finish = x;
  _finish++;
}

当探索并深入了解了STL中的vector容器后,我们不禁感叹其强大的功能和灵活性。随着对vector的学习和使用,我们逐渐理解到,一个高效的C++程序不仅仅是代码的堆砌,更是对数据结构、算法和STL等标准库深刻理解的体现。vector的迭代器、容量管理、元素访问以及算法支持等功能,都是我们在日常编程中不可或缺的工具


📖4. 总结

学习vector仅仅是开始。STL(Standard Template Library)还提供了诸如list、set、map等其他强大的容器,每个都有其独特的特点和适用场景。因此,鼓励大家继续深入学习STL,探索其背后的设计理念和实现原理。通过不断实践,我们不仅能够提高编程效率,还能够培养出更加优雅、健壮的代码风格。最后,我想说的是学习是一个永无止境的过程。无论是STL还是其他任何技术,都值得我们不断学习和探索。让我们保持对知识的渴望和好奇心,不断前行,在编程的道路上越走越远

目录
相关文章
|
1月前
|
缓存 算法 程序员
C++STL底层原理:探秘标准模板库的内部机制
🌟蒋星熠Jaxonic带你深入STL底层:从容器内存管理到红黑树、哈希表,剖析迭代器、算法与分配器核心机制,揭秘C++标准库的高效设计哲学与性能优化实践。
C++STL底层原理:探秘标准模板库的内部机制
|
8月前
|
编译器 C++ 容器
【c++丨STL】基于红黑树模拟实现set和map(附源码)
本文基于红黑树的实现,模拟了STL中的`set`和`map`容器。通过封装同一棵红黑树并进行适配修改,实现了两种容器的功能。主要步骤包括:1) 修改红黑树节点结构以支持不同数据类型;2) 使用仿函数适配键值比较逻辑;3) 实现双向迭代器支持遍历操作;4) 封装`insert`、`find`等接口,并为`map`实现`operator[]`。最终,通过测试代码验证了功能的正确性。此实现减少了代码冗余,展示了模板与仿函数的强大灵活性。
245 2
|
8月前
|
存储 算法 C++
【c++丨STL】map/multimap的使用
本文详细介绍了STL关联式容器中的`map`和`multimap`的使用方法。`map`基于红黑树实现,内部元素按键自动升序排列,存储键值对,支持通过键访问或修改值;而`multimap`允许存在重复键。文章从构造函数、迭代器、容量接口、元素访问接口、增删操作到其他操作接口全面解析了`map`的功能,并通过实例演示了如何用`map`统计字符串数组中各元素的出现次数。最后对比了`map`与`set`的区别,强调了`map`在处理键值关系时的优势。
470 73
|
9月前
|
存储 缓存 C++
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
|
9月前
|
算法 编译器 C++
模拟实现c++中的vector模版
模拟实现c++中的vector模版
|
8月前
|
存储 算法 C++
【c++丨STL】set/multiset的使用
本文深入解析了STL中的`set`和`multiset`容器,二者均为关联式容器,底层基于红黑树实现。`set`支持唯一性元素存储并自动排序,适用于高效查找场景;`multiset`允许重复元素。两者均具备O(logN)的插入、删除与查找复杂度。文章详细介绍了构造函数、迭代器、容量接口、增删操作(如`insert`、`erase`)、查找统计(如`find`、`count`)及`multiset`特有的区间操作(如`lower_bound`、`upper_bound`、`equal_range`)。最后预告了`map`容器的学习,其作为键值对存储的关联式容器,同样基于红黑树,具有高效操作特性。
374 3
|
9月前
|
存储 算法 C++
【c++丨STL】priority_queue(优先级队列)的使用与模拟实现
本文介绍了STL中的容器适配器`priority_queue`(优先级队列)。`priority_queue`根据严格的弱排序标准设计,确保其第一个元素始终是最大元素。它底层使用堆结构实现,支持大堆和小堆,默认为大堆。常用操作包括构造函数、`empty`、`size`、`top`、`push`、`pop`和`swap`等。我们还模拟实现了`priority_queue`,通过仿函数控制堆的类型,并调用封装容器的接口实现功能。最后,感谢大家的支持与关注。
544 1
|
10月前
|
C++ 容器
【c++丨STL】stack和queue的使用及模拟实现
本文介绍了STL中的两个重要容器适配器:栈(stack)和队列(queue)。容器适配器是在已有容器基础上添加新特性或功能的结构,如栈基于顺序表或链表限制操作实现。文章详细讲解了stack和queue的主要成员函数(empty、size、top/front/back、push/pop、swap),并提供了使用示例和模拟实现代码。通过这些内容,读者可以更好地理解这两种数据结构的工作原理及其实现方法。最后,作者鼓励读者点赞支持。 总结:本文深入浅出地讲解了STL中stack和queue的使用方法及其模拟实现,帮助读者掌握这两种容器适配器的特性和应用场景。
269 21
|
9月前
|
存储 算法 C++
深入浅出 C++ STL:解锁高效编程的秘密武器
C++ 标准模板库(STL)是现代 C++ 的核心部分之一,为开发者提供了丰富的预定义数据结构和算法,极大地提升了编程效率和代码的可读性。理解和掌握 STL 对于 C++ 开发者来说至关重要。以下是对 STL 的详细介绍,涵盖其基础知识、发展历史、核心组件、重要性和学习方法。
|
11月前
|
编译器 C语言 C++
【c++丨STL】list模拟实现(附源码)
本文介绍了如何模拟实现C++中的`list`容器。`list`底层采用双向带头循环链表结构,相较于`vector`和`string`更为复杂。文章首先回顾了`list`的基本结构和常用接口,然后详细讲解了节点、迭代器及容器的实现过程。 最终,通过这些步骤,我们成功模拟实现了`list`容器的功能。文章最后提供了完整的代码实现,并简要总结了实现过程中的关键点。 如果你对双向链表或`list`的底层实现感兴趣,建议先掌握相关基础知识后再阅读本文,以便更好地理解内容。
254 1

热门文章

最新文章