当前位置: 首页 > news >正文

【C++】STL -- vector 的使用及模拟实现

本篇文章主要讲解 vector 容器的使用及模拟实现

        vector 是矢量的意思,在 STL 中是一个容器,也就是我们之前常说的顺序表,比起 C 语言中的数组,C++ 中的 vector 可以进行增容与动态增加数据。


目录

1  vector 的使用

1) vector 的定义

2) vector 元素的遍历与访问

(1) 迭代器

(2) Element Access 接口

3) vector 容量接口

4) vector 增删查改接口

2  vector 迭代器失效现象

1) 使用引起底层空间改变的接口

2) erase 之后可能引起迭代器失效

3) string 也会发生迭代器失效

3  vector 的模拟实现

1) 实现逻辑

(1) vector 的成员函数

(2) 构造函数、析构函数、拷贝构造与赋值运算符重载函数

(3) 迭代器、size、capacity 与 operator[]

(4) 扩容与改变对象接口

2) 代码实现

4  动态二维数组

5  总结


1  vector 的使用

        我们依旧是像学习 string 一样学习 vector,我们先通过阅读文档来学习 vector 的常用接口有哪些。

vector 不同于 string,其是一个类模板,所以在使用前,我们必须显示实例化,比如:vector<int>,vector<string>。其实,string 也是一个类模板 typedef 来的:

所以 string 也是由模板得来的,只不过其已经实例化过了,就不需要我们实例化了。而 vector 使用前必须先进行实例化。

1) vector 的定义

在这里我们主要学习 vector 的构造函数有哪些就可以了:

可以看到 vector 的构造函数有很多,其中重点是无参构造,拷贝构造以及使用 initializer_list 构造,这里的 initializer_list 你就可以看成是一个数组。如:

#include<iostream>
#include<vector>using namespace std;int main()
{//无参构造//使用 vector 必须模板显示实例化vector<int> v1;//下面两个都是使用 initializer_list 进行构造vector<int> v2 = {1, 2, 3, 4, 5};vector<int> v3({1, 3, 5, 7, 9});//拷贝构造vector<int> v4(v3);//vector 中不仅仅是 int,也可以是别的类型vector<string> v5 = {"left", "right", "find"};//vector 中也有迭代器,所以也可以使用范围 for 遍历for (auto& x : v2)cout << x << ' ';cout << endl;//这里如果不加引用,那就是拷贝,效率较低for (auto& s : v5)cout << s << ' ';cout << endl;return 0;
}

输出结果:

1 2 3 4 5
left right find

2) vector 元素的遍历与访问

        vector 元素的遍历与访问也是和 string 一样具有两种方式,一种是迭代器,一种是 Element Access 接口。

(1) 迭代器

vector 迭代器的使用与 string 一模一样,这里就不再赘述,我们直接使用:

#include<iostream>
#include<vector>using namespace std;int main()
{vector<int> v1 = {1, 2, 3, 4, 5};auto it = v1.begin();//也可以使用迭代器改变元素*it = 10;while (it != v1.end()){cout << *it << ' ';++it;}cout << endl;//也可以使用范围 forfor (auto& x : v1){//范围 for 参数使用引用也可以改变元素++x;cout << x << ' ';}cout << endl;//如果是 const 对象就不能改变了const vector<int> v2 = {1, 2, 3, 4, 5};auto it1 = v2.begin();//这里无法改变。如果改变会报错//*it1 = 10;return 0;
}

输出结果:

10 2 3 4 5
11 3 4 5 6

(2) Element Access 接口

其中 operator[],at,front,back 与 string 中的接口功能一模一样,这里就不再赘述。我们来讲解一下 data 接口。

由上述描述可知,data 其实是会返回 vector 底层存储空间的起始地址,如:

#include<iostream>
#include<vector>using namespace std;int main()
{vector<int> v1 = { 1, 2, 3, 4, 5 };int* ptr = v1.data();*ptr = 10;++ptr;*ptr = 30;ptr[3] = 50;for (auto& x : v1)cout << x << ' ';cout << endl;return 0;
}

输出结果:

10 30 3 4 50

但是值得注意的一点是,data 的指针在 vector 没有发生更改对象之前是有效的,但是当发生了插入或者删除操作时,vector 底层可能会发生扩容现象,此时之前 data 返回的指针就有可能会失效,所以此时要注意 data 指针的失效问题(与后面要进行讲解的迭代器失效很像)。所以建议大家还是使用 operator[] 或者迭代器来访问元素。


3) vector 容量接口

vector 的容量接口与 string 可以说是一模一样,各种接口的名称与功能也都是一样的,这里也就不再赘述。


4) vector 增删查改接口

这里的 push_back、pop_back、insert、erase 接口与 string 中的接口也一模一样。注意在 vector 中新增了两个接口 emplace 与 emplace_back:

这两个接口也很重要,但是现在无法讲解其原理,只有在讲解完可变参数之后,才可以讲解其原理,这里只需会使用即可,而且 emplace_back 与 emplace 会比 push_back 与 insert 效率更高。他们的使用区别主要体现在插入自定义类型的对象上面,插入内置类型时用法相同:

#include<iostream>
#include<vector>using namespace std;class A
{
public:A(int a1 = 1, int a2 = 2):_a1(a1),_a2(a2){}void Print(){cout << _a1 << ' ' << _a2 << endl;}
private:int _a1;int _a2;
};int main()
{vector<int> v1;//添加想要尾插的数据//内置类型插入时使用是相同的v1.emplace_back(1);for (auto& x : v1)cout << x << ' ';cout << endl;v1.emplace(v1.begin(), 2);for (auto& x : v1)cout << x << ' ';cout << endl;cout << "**************" << endl;//如果是自定义数据,使用就不一样了vector<A> v2;//使用 push_back 与 insert 时必须插入一个对象,这里是先用 {1,1} 构造一个临时对象//再将对象插入到容器中v2.push_back({ 1, 1 });v2.insert(v2.end(), A(2,2));for (auto& a : v2){a.Print();}cout << endl;//emplace 系列直接构造对象插入就可以,不需要临时对象,使用时不加{}v2.emplace_back(3, 3);v2.emplace(v2.end(), 4, 4);for (auto& a : v2){a.Print();}return 0;
}

2  vector 迭代器失效现象

        vector 中存在一种重要的现象,就是迭代器失效。如果我们不注意,很容易造成野指针访问。接下来我们就来看一下这种现象。

        vector 的迭代器底层就类似于 string 的迭代器,底层都是封装了指针,所谓的迭代器失效,其实就是底层指针指向的空间已经被释放了,不再指向有效空间了,再次使用的话就会造成野指针访问,在上层也就表现为了迭代器失效。以下是会造成迭代器失效的具体场景:

1) 使用引起底层空间改变的接口

       在 vector 中,一些接口由于其需求(比如扩容)可能会改变其底层内存空间大小,此时可能会引起迭代器失效。比如:resize,reserve,insert,push_back等等:

#include<iostream>
#include<vector>using namespace std;int main()
{vector<int> v = {1, 2, 3, 4, 5, 6};//此时迭代器是有效的auto it = v.begin();while (it != v.end()){cout << *it << ' ';++it;}cout << endl;//下面的接口都可能会引起的底层空间扩容,导致迭代器失效//v.push_back(7);//v.insert(v.begin(), 7);//v.reserve(100);//v.assign(100, 7);v.resize(100, 7);//此时迭代器失效了while (it != v.end()){cout << *it << ' ';++it;}cout << endl;return 0;
}

​运行结果:

        迭代器之所以失效,就是因为这些接口会导致 vector 底层空间扩容,可能会释放旧空间,开辟新空间,而之前的迭代器还指向被释放的空间,所以操作的是之前被释放了的空间。所以要想解决迭代器失效,只需要对迭代器重新赋值就可以了

#include<iostream>
#include<vector>using namespace std;int main()
{vector<int> v = {1, 2, 3, 4, 5, 6};//此时迭代器是有效的auto it = v.begin();while (it != v.end()){cout << *it << ' ';++it;}cout << endl;//下面的接口都可能会引起的底层空间扩容,导致迭代器失效//v.push_back(7);//v.insert(v.begin(), 7);//v.reserve(100);//v.assign(100, 7);v.resize(100, 7);//重新赋值就可以避免迭代器失效it = v.begin();while (it != v.end()){cout << *it << ' ';++it;}cout << endl;return 0;
}

输出结果:

1 2 3 4 5 6
1 2 3 4 5 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7

2) erase 之后可能引起迭代器失效

        在 vector 中有个方法是 erase,该方法用来删除一个迭代器位置的元素或者是一个迭代器区间内的元素。erase 在删除完 pos 位置的元素后,pos 位置之后的元素会向前挪动,此时 pos 迭代器并不会失效。但是如果 pos 指向的是最后一个元素,也就是 end() - 1,此时删除完 pos 位置元素后,pos 成为了 end(),但是 end() 位置是不应该有元素的,此时 pos 位置迭代器就失效了。既然有失效的情况,也有不失效的情况,此时不同编译器的处理方法是不同的,在 vs 中,统一认为 erase 之后迭代器就失效了,需要重新对迭代器赋值;而在 g++ 中,则根据具体情况判断迭代器是否失效

#include<iostream>
#include<vector>using namespace std;int main()
{vector<int> v = { 1, 2, 3, 4, 5, 6 };auto pos = find(v.begin(), v.end(), 3);//在 erase 之后,迭代器就失效了v.erase(v.begin());cout << *pos << endl;return 0;
}

运行结果:

        所以为了避免 erase 之后迭代器会失效,erase 函数其实是有返回值的,其会返回删除后下一个位置的迭代器

所以使用 vector 删除偶数元素应该这样写:

#include<iostream>
#include<vector>using namespace std;int main()
{vector<int> v;//输入数据,最后以0结尾int x;cin >> x;while (x){v.push_back(x);cin >> x;}auto it = v.begin();while (it != v.end()){if (*it % 2 == 0)it = v.erase(it);else++it;}for (auto& e : v)cout << e << ' ';cout << endl;return 0;
}

而不是:

#include<iostream>
#include<vector>using namespace std;int main()
{vector<int> v;//输入数据,最后以0结尾int x;cin >> x;while (x){v.push_back(x);cin >> x;}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;
}

3) string 也会发生迭代器失效

        所以与 vector 类似,当 string 中的方法在进行插入操作,使得底层空间扩容时,也有可能发生迭代器失效现象,erase 删除任意位置的迭代器之后也会使得该迭代器失效。


3  vector 的模拟实现

1) 实现逻辑

(1) vector 的成员函数

        在 vector 的模拟实现中,我们依然可以采取像 string 那样使用一个 _str 指针,一个 size_t _size,一个 size_t _capacity 来实现,毕竟 vector 也是一个顺序表,而 string 其实就是一个 char 类型的顺序表,那么我们可以看一下源码中的 vector 是如何实现的,在源码中利用了继承的知识,设计了一个基类,一个派生类(继承后面会进行讲解,这里只是大致看一下结构):

template<typename _Tp, typename _Alloc>
struct _Vector_base
{struct _Vector_impl : public _Tp_alloc_type{pointer _M_start;pointer _M_finish;pointer _M_end_of_storage;}
};template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
class vector : protected _Vector_base<_Tp, _Alloc>
{}

这里我只取出了跟 vector 成员变量有关的部分,通过源码我们可以看到源码中的 vector 成员变量其实是三个指针,所以我们也可以将成员变量设计为 3 个指针;另外,vector 中不仅仅只可以存放整型,也可以存放自定义类型,所以我们应设计为模板,所以我们设计的 vector 整体框架就是下面这样的:

template<class T>
class vector
{
public://成员函数
private://_start 指向空间起始位置T* _start;//_finish 指向有效元素的下一个位置T* _finish;//_end_of_storage 指向空间结束位置T* _end_of_storage;
};

既然设计为了模板,所以我们就无法将成员函数的声明与定义实现在两个文件,不然是会报链接错误的,所以我们把成员函数实现在一个 vector.h 的文件里就可以了。


(2) 构造函数、析构函数、拷贝构造与赋值运算符重载函数

构造函数

        构造函数我们会实现

析构函数

        析构函数比较简单,既然是 _start 申请了资源,只需要 delete[] _start 就可以了,之后让 _start = _finish = _end_of_storage = nullptr 就可以了。

拷贝构造

        如果采用编译器自动生成的拷贝构造函数,实现的是浅拷贝,依然与 string 一样会发生多次释放与一个影响另一个的问题:

所以这里我们依然要自己实现深拷贝的拷贝构造函数。实现拷贝构造函数我们可以复用之后的代码,我们可以先 reserve(v.size())(这里 v 为要进行拷贝构造的对象)提前扩容,避免重复扩容影响效率,之后再将 v 的数据一个一个 push_back 进去就可以了。

赋值重载函数

        赋值重载依然分为经典写法和现代写法。经典写法既然也是深拷贝,其实与拷贝构造差不多,只是在开辟新空间之前,我们需要先释放之前的空间,否则会造成内存泄露。现代写法与 string 的现代写法一样的:

//交换函数
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)
{swap(v);return *this;
}

在这里,就是在调用赋值运算符重载函数时,由于写的是值传参,所以会调用拷贝构造函数,v 为赋值对象的拷贝,这样交换完 *this 和 v 之后,*this 就接管了之前 v 的空间,v 接管了 *this 的空间,退出函数之后,会调用析构函数来释放 v 的空间,这样不仅达到了拷贝赋值的作用,还将原空间释放了,一举两得。


(3) 迭代器、size、capacity 与 operator[]

迭代器

        这里实现迭代器我们依然像 string 一样,采用指针的形式:

typedef T* iterator;
typedef const T* const_iterator;

这样我们只需要实现 begin() 与 end() 就可以了,begin() 返回 _start,end() 返回 _finish。

size

        size 也比较简单,既然是有效元素的个数,只需要用 _finish - _start 就是有效元素的个数。

capacity

        capacity 也很简单,空间大小只需要用 _end_of_storage - _start 就是开辟空间的大小。

operator[]

        operator[] 是返回对应下标的元素,底层只要返回 _start[pos] 就可以了。


(4) 扩容与改变对象接口

reserve

        vector 的 reserve 与 string 中的 reserve 其实差不多,都是当 n > capacity 时,才会进行扩容。扩容只需要借助一个 T* 类型的指针 tmp,让 tmp = new T[n],然后拷贝数据,再释放数据,再让 _start = tmp 就可以了,但是这里与 string 有一个不同,那就是在释放数据之前,必须先保存一下之前的有效数据个数,也就是 _finish - _start,否则 _finish 无法更新

resize

        与 string 一样,如果 n < size(),那就直接让 _finish = _start + n 就可以了;如果 n > size(),那就 reserve(n),即先进行扩容,因为只有当 n > size() 时,才会扩容,所以正好会满足 resize 的扩容逻辑,扩容之后,在后面添加数据就可以了。

push_back

        既然是要插入数据,那么在这之前就要先看是否需要扩容,如果满了就需要先进行扩容;扩容之后直接 *_finish = val,++_finish 就可以了。

pop_back

        要删除数据,首先我们需要先检查 vector 中是否有数据,如果没有,那么我们就断言报错;检查完之后,直接 --_finish 就完成了尾删。

insert

        首先我们需要对要进行插入的位置 pos 之前的位置进行判断,判断 pos 是否满足 begin() <= pos <= end();判断完成之后,既然是要插入数据,那么我们就要像 push_back 一样判断剩余空间能否插入数据,需要进行扩容,但是在这里有一个需要注意的点,那就是扩容之后,原来的空间被释放,而要插入的 pos 位置还是指向了被释放的空间,所以这里的 pos 迭代器就失效,所以在扩容时,我们要保存一下 len = pos - _start,扩容完之后,要对 pos 重新赋值,即 pos = _start + len,这样才能避免迭代器失效;后面就是挪动数据,插入数据了,这里就不再赘述了。

erase

        erase 实现逻辑与 string 中 erase 方法实现逻辑是相同的,都是先判断位置的合理性以及 vector 中是否有数据;之后再进行挪动数据就可以了。这里值得注意的是我们在 string 中模拟实现的 erase 是没有返回值的(但是其实在 std::string 中 erase 是有返回值的,也是返回 iterator),而在 vector 中我们需要返回删除元素下一个位置的迭代器。


2) 代码实现

vector.h

#pragma once
#include<iostream>
#include<assert.h>using namespace std;namespace LTL
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;//无参构造vector(){}//n个 val 构造vector(size_t n, const T& val = T()){resize(n, val);}vector(int n, const T& val = T()){resize(n, val);}//迭代器区间构造template<class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}//initializer_list 构造vector(const initializer_list<T>& il){//直接用范围 for 遍历il,尾插元素就可以for (auto& x : il){push_back(x);}}//拷贝构造//这里要实现深拷贝vector(const vector<T>& v){reserve(v.size());for (size_t i = 0; i < v.size(); i++)push_back(v[i]);}//赋值重载//依然要深拷贝//vector<T>& operator=(const vector<T>& v)//{//	if (this != &v)//	{//		//释放原空间//		delete[] _start;//		_start = _finish = _end_of_storage = nullptr;//		reserve(v.size());//		for (size_t i = 0; i < v.size(); ++i)//			push_back(v[i]);//	}//	return *this;//}//现代写法vector<T>& operator=(vector<T> v){swap(v);return *this;}//析构函数~vector(){delete[] _start;_start = _finish = _end_of_storage = nullptr;}//交换函数void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);}//实现迭代器iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}size_t size(){return _finish - _start;}size_t capacity(){return _end_of_storage - _start;}size_t size() const{return _finish - _start;}size_t capacity() const{return _end_of_storage - _start;}T& operator[](size_t pos){assert(pos < size());return _start[pos];}const T& operator[](size_t pos) const{assert(pos < size());return _start[pos];}void resize(size_t n, const T& val = T()){if (n < size()){_finish = _start + n;}else{reserve(n);while (_finish != _start + n){*_finish = val;++_finish;}}}void reserve(size_t n){if (n > capacity()){//需要先保存之前的有效数据个数size_t oldsize = size();//开辟空间T* tmp = new T[n];//拷贝数据for (size_t i = 0; i < oldsize; ++i){tmp[i] = _start[i];}//释放原空间,指向新空间delete[] _start;_start = tmp;_finish = _start + oldsize;_end_of_storage = _start + n;}}void push_back(const T& val){//满了先扩容if (_finish == _end_of_storage){//2 倍扩容size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newCapacity);}//插入数据*_finish = val;++_finish;}void pop_back(){assert(_finish > _start);--_finish;}void insert(iterator pos, const T& val){assert(pos >= _start);assert(pos <= _finish);//满了需要扩容,扩容之后迭代器可能失效,需要更新 posif (_finish == _end_of_storage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : 2 * capacity());pos = _start + len;}//挪动数据for (iterator it = _finish; it > pos; --it){*it = *(it - 1);}*pos = val;++_finish;}iterator erase(iterator pos){assert(size() > 0);assert(pos >= _start);assert(pos < _finish);//先挪动数据for (iterator it = pos; it < _finish - 1; ++it){*it = *(it + 1);}--_finish;return pos;}private:T* _start = nullptr;T* _finish = nullptr;T* _end_of_storage = nullptr;};
}

测试用例

#include"vector.h"
#include<string>//测试构造函数,拷贝构造与赋值重载
void Test_vector01()
{//构造函数LTL::vector<int> v1;LTL::vector<int> v2(10, 1);//范围forfor (auto& x : v2){cout << x << ' ';}cout << endl;LTL::vector<int> v3(v2.begin() + 1, v2.end() - 1);//范围forfor (auto& x : v3){cout << x << ' ';}cout << endl;LTL::vector<int> v4 = {1, 2, 3, 4, 5, 6};//范围forfor (auto& x : v4){cout << x << ' ';}cout << endl;cout << "***********************" << endl;//拷贝构造LTL::vector<int> v5(v4);//范围forfor (auto& x : v5){++x;cout << x << ' ';}cout << endl;//值不同就是深拷贝for (auto& x : v4){cout << x << ' ';}cout << endl;//赋值重载LTL::vector<int> v6({ 1,3,5,6,9 });v6 = v4;//范围forfor (auto& x : v6){cout << x << ' ';}cout << endl;
}//测试元素的遍历
void Test_vector02()
{LTL::vector<int> v1 = { 1, 2, 3, 4, 5, 6, 7, 8 };cout << v1.size() << endl;cout << v1.capacity() << endl;//迭代器auto it = v1.begin();while (it != v1.end()){cout << *it << ' ';++it;}cout << endl;//operator[]for (size_t i = 0; i < v1.size(); i++){cout << v1[i] << ' ';}cout << endl;
}//测试插入与删除接口
void Test_vector03()
{LTL::vector<int> v1 = { 1, 2, 3, 4, 5, 6 };v1.resize(100, 7);for (auto& x : v1)cout << x << ' ';cout << endl;LTL::vector<int> v2 = { 1, 2, 3, 4, 5, 6 };v2.reserve(100);cout << v2.size() << endl;cout << v2.capacity() << endl;LTL::vector<int> v3;v3.push_back(1);v3.push_back(2);v3.push_back(3);v3.push_back(4);v3.push_back(5);v3.push_back(6);v3.push_back(7);for (auto& x : v3)cout << x << ' ';cout << endl;v3.pop_back();v3.insert(v3.begin() + 2, 10);for (auto& x : v3)cout << x << ' ';cout << endl;v3.insert(v3.begin(), 11);for (auto& x : v3)cout << x << ' ';cout << endl;v3.insert(v3.end(), 12);for (auto& x : v3)cout << x << ' ';cout << endl;v3.erase(v3.begin());for (auto& x : v3)cout << x << ' ';cout << endl;v3.erase(v3.begin() + 2);for (auto& x : v3)cout << x << ' ';cout << endl;v3.erase(v3.end() - 1);for (auto& x : v3)cout << x << ' ';cout << endl;
}//测试其他类型
void Test_vector04()
{LTL::vector<string> v1;v1.push_back("left");v1.push_back("right");v1.push_back("sort");v1.push_back("find");v1.push_back("reverse");v1.push_back("reserve");v1.push_back("iterator");for (auto& s : v1)cout << s << ' ';cout << endl;v1.pop_back();v1.insert(v1.begin(), "hello");v1.insert(v1.begin() + 2, "world");v1.insert(v1.end(), "algorithm");for (auto& s : v1)cout << s << ' ';cout << endl;v1.erase(v1.begin());v1.erase(v1.begin() + 2);v1.erase(v1.end() - 1);for (auto& s : v1)cout << s << ' ';cout << endl;
}int main()
{//Test_vector01();//Test_vector02();//Test_vector03();Test_vector04();return 0;
}

4  动态二维数组

        在 C 语言中存在二维数组,在 C++ 中也存在二维数组 vector<vector<T>>,只不过不同于 C 语言的是,C++ 的二维数组更加方便使用,可以进行动态扩容,接下来我们通过一道题目来理解一下 C++ 中的动态二维数组:

杨辉三角:https://leetcode.cn/problems/pascals-triangle/

题目描述

给定一个非负整数 numRows生成「杨辉三角」的前 numRows 行。

在「杨辉三角」中,每个数是它左上方和右上方的数的和。

示例 1:

输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]

示例 2:

输入: numRows = 1
输出: [[1]]

提示:

  • 1 <= numRows <= 30

题目解析

        该题目就是给你一个行数,返回存储着该行数杨辉三角的二维数组,其中每一行自己单独构成一个一维数组。

算法讲解

        这个题目看上去是一个三角,实际上输出这个杨辉三角的时候是这样的,以 5 行为例:

1

1 1

1 2 1

1 3 3 1

1 4 6 4 1

所以我们只要创建一个 vector<vector<int>> vv,然后在里面开辟 5 个 vector<int>,每个 vector<int> 又只要开辟 i 个空间(假设 i 为行数),让开始元素和结尾元素都是1,然后再让

vv[i][j] = vv[i-1][j-1] + vv[i-1][j](i为行数的下标,j为列数的下标,2 <= i < numRows,1 <= j < i)。如果是在 C 语言中,vv[i][j] 其实是指针的解引用,也就是 *(*(vv + i) + j),而在 C++ 中,vv[i][j] 其实是 vv.operator[](i).operator[](j),并且在 C++ 中动态二维数组的结构是这样的:

代码

class Solution 
{
public:vector<vector<int>> generate(int numRows) {vector<vector<int>> vv(numRows);for (int i = 0; i < numRows;++i)vv[i].resize(i + 1, 1);for (int i = 2; i < numRows; ++i)for (int j = 1; j < i; j++)vv[i][j] = vv[i-1][j-1] + vv[i-1][j];return vv;   }
};

5  总结

        可以看到模拟实现了 string 之后,其实 vector 的模拟实现也很简单了,基本上与 string 是一样的,值得注意的是,vector 的迭代器失效现象非常重要,请大家一定要理解并掌握。

http://www.dtcms.com/a/427235.html

相关文章:

  • 网站如何做图片特效erp软件实施
  • 【28】C# WinForm入门到精通 ——多文档窗体MDI【属性、方法、实例、源码】【多窗口重叠、水平平铺、垂直平铺、窗体传值】
  • 贡井区建设局网站淘宝客做自己的网站
  • 蓝牙发展史
  • 对LED点灯实验的C与汇编的深入分析,提及到volatile
  • 网站建设外包广州网站建设说说外链的建设
  • LevOJ P2080 炼金铺 II [矩阵解法]
  • wordpress网站映射wordpress免费网站国外
  • 哈尔滨企业建站系统西宁建设局官方网站
  • py_innodb_page_info.py表空间分析
  • 有什么做宝宝辅食的网站吗莱阳网站开发
  • tasklet
  • 页面 HTTPS 化实战,从证书部署到真机验证的全流程(证书链、重定向、混合内容、抓包排查)
  • 北京哪家公司做网站电脑上买wordpress
  • 家用机能否做网站服务器泰安房价走势图
  • MC33PT2000控制详解七:软件代码设计1-图形化设置
  • 在租用香港服务器之前如何判断质量
  • 【高清视频】CXL 2.0 over Fibre演示和答疑 - 将内存拉到服务器10米之外
  • 【STM32项目开源】基于STM32的独居老人监护系统
  • 海外服务器怎么测试再不同地方的访问速度?
  • Linux最忙CPU组查找函数和最忙运行队列查找函数
  • 查看未知LiDAR设备的IP地址
  • iOS 0Day漏洞CVE-2025-24085相关PoC利用细节已公开
  • 网站文案框架网络推广策划书
  • 基于Chrome140的FB账号自动化——运行脚本(三)
  • 广州做网站设计镇江建筑公司排名最新
  • RHCA - CL260 | Day12:集群性能建议
  • CNN基础学习(自用)
  • Spring Boot 集成 Kafka 详解
  • MQTT数据集成