鲜花网站建设策划方案,游戏网站建设论文,建立简单网站,应用之星在线制作app难道向上攀爬的那条路#xff0c;不是比站在顶峰更让人热血沸腾吗#xff1f; 文章目录一、vector和string的联系与不同二、vector的扩容操作1.resize() #xff08;缺省值为匿名对象#xff09; reserve()2.reserve在g和vs上的扩容机制3.reserve异地扩容和shri…难道向上攀爬的那条路不是比站在顶峰更让人热血沸腾吗 文章目录一、vector和string的联系与不同二、vector的扩容操作1.resize() 缺省值为匿名对象 reserve()2.reserve在g和vs上的扩容机制3.reserve异地扩容和shrink_to_fit异地缩容的设计理念4.vector和malloc分别实现动态开辟的二维数组三、vector的元素访问操作1.operator[]和at对于越界访问的检查机制一段经典的代码错误四、vector的修改操作1.assign和迭代器的配合使用2.insert和find的配合使用3.类外、类内、算法库的3个swap五、看源码时需要注意的问题一、vector和string的联系与不同
1. vector底层也是用动态顺序表实现的和string是一样的但是string默认存储的就是字符串而vector的功能较为强大一些vector不仅能存字符理论上所有的内置类型和自定义类型都能存vector的内容可以是一个自定义类型的对象也可以是一个内置类型的变量。
2. vector在使用时需要进行类模板的实例化因为传递的模板参数不同则vector存储的元素类型就会有变化所以在使用vector的时候要进行类模板的显式实例化。 类模板的第二个参数是空间配置器这个学到后面再说而且这个参数是有缺省值的我们只用这个缺省值就欧克了所以在使用vector时只需要关注第一个参数即可。 void test_vector1()
{string s;vectorint v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);//string和vector底层都是用数组实现的所以他们都支持迭代器、范围for、下标[]的遍历方式for (size_t i 0; i v.size(); i){cout v[i] ;//string和vector的底层都是数组所以可以使用[]但list就不能使用[]了所以万能的方法是迭代器。}cout endl;vectorint::iterator it v.begin();//iterator实际是某种类型的重定义在使用时要指定好类域。while (it ! v.end()){cout *it ;it;}cout endl;for (auto e : v)//不就是迭代器吗{cout e ;}cout endl;cout v.max_size() endl;//int是10亿多因为int占4个字节42亿÷4。cout s.max_size() endl;//max_size的大小是数据的个数我的编译器的char是21亿多。不用管他这接口没价值。//vectorchar vstr;//string str;//vectorchar不能替代string即使两者都是字符数组也不行因为string有\0
}二、vector的扩容操作
1.resize() 缺省值为匿名对象 reserve()
1. 对于string和vectorreserve和resize是独有的因为他们的底层都是动态顺序表实现的list就没有reserve和resize因为他底层是链表嘛。
2. 对于reserve这个函数来说官方并没有将其设定为能够兼容实现缩容的功能明确规定这个函数在其他情况下例如预留空间要比当前小的情况下这个函数的调用是不会引起空间的重新分配的也就是说容器vector的capacity是不会被影响的。 3. 有的人可能认为缩容只要丢弃剩余的空间就好了但其实没有那么简单你从C语言阶段free空间不能分两次free进行释放就可以看出来一块已经申请好的空间就是一块儿独立的个体不能说你保留空间的一部分丢弃剩余的一部分这样是不行的本质上和操作系统的内存管理有关系如果对这部分知识有兴趣可以下去研究一下。
4. 但值得注意的是缩容表面看起来是降低了空间的使用率想要提高程序的效率但实际上并未提高效率缩容是需要异地缩容的需要重新开空间和拷贝数据代价不小所以平常不建议对空间进行缩容。 5. vector的resize和string的resize同样具有三种情况但vector明显功能比string要更健壮一些string类型只能针对于字符而vector在使用resize进行初始化空间数据时对内置类型和自定义类型均可以调用对应的拷贝构造来初始化所以其功能更为健壮默认将整型类型初始化为0指针类型初始化为空指针。 void test_vector2()
{vectorint v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);//resize和reserve对于vector和string是独有的对于list而言就没有reserve和resizecout v.capacity() endl;v.reserve(10);cout v.capacity() endl;v.reserve(4);cout v.capacity() endl;//并不会缩容缩容并不会提高效率缩容是有代价的某种程度上就是以空间换时间。v.resize(8);//int、指针这些内置类型的默认构造就把他们初始化为0和空指针这些。v.resize(15, 1);v.resize(3);//不缩容也是采用惰性删除的方式将size调整为3就可以了显示数组内容的时候按照size大小显示就可以了。
}2.reserve在g和vs上的扩容机制
1. 在vs上扩容机制采用1.5倍的大小g上采用2倍的大小对于空间的扩容如果开大了会造成空间浪费开小了不够用又会导致频繁扩容带来性能的损耗而2倍的大小可以说是刚刚好至于微软的工程师为什么选择1.5来进行扩容是由于内存的某种对其因素导致。 void test_vector_expand()//扩容机制大概是1.5倍进行扩容
{size_t sz;vectorint v;//v.reserve(100);//已知开辟空间大小时我们应该调用reserve来提前预留空间进行扩容。sz v.capacity();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;}}
}3.reserve异地扩容和shrink_to_fit异地缩容的设计理念
1. 对于reserve的设计理念就是不去缩容就算手动调用reserve进行缩容编译器也不会理你空间的大小始终都不会变capacity的值一直是不动的这样的设计理念本质上就是用空间来换时间因为异地缩容需要开空间和拷贝数据比较浪费时间。
2. 相反shrink_to_fit就是缩容函数强制性的将capacity的大小降低到适配size大小的值它的设计理念就是以空间来换时间但日常人们所使用的手机或者PC空间实际上是足够的不够的是时间所以这种函数还是不要使用的为好除非说你后面肯定不会插入数据了不再进行任何modify操作那你可以试着将空间还给操作系统减少空间的使用率。
void test_vector7()
{vectorint v;v.reserve(10);v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);cout v.size() endl;cout v.capacity() endl;//C不会太推荐使用malloc来进行空间的初始化了因为很有可能存在自定义类型对象没有初始化的问题如果用new会自动调用构造。//设计理念就是不去缩容因为异地缩容的代价很大所以就算你用reserve或是resize调整大小以改变capacity但编译器不会管你。// 因为它的设计理念不允许它这么做而遇到shrink_to_fit就没辙了因为他是缩容函数//---不动空间不去缩容以空间换时间的设计理念因为缩容虽然空间资源多了但是时间就长了为了提高时间就用空间换。//shrink_to_fit就是反面的函数进行了缩容。v.reserve(3);cout v.size() endl;cout v.capacity() endl;//设计理念以时间换空间一般缩容都是异地缩容代价不小一般不要轻易使用。通常情况下我们是不缺空间的缺的是时间。v.shrink_to_fit();//缩容函数代价很大通常的缩容的方式就是找一块新的较小的空间然后将原有数据拷贝进去v.resize(3);cout v.size() endl;cout v.capacity() endl;v.clear();//clear都是不动空间的
}4.vector和malloc分别实现动态开辟的二维数组
杨辉三角
1. 对于C语言实现的话需要一个返回值和两个输出型参数来返回到后台接口里面第一个参数代表二维数组的大小这道题我们知道返回的二维数组的大小但其他题是有可能不知道的而leetcode的后台测试用例是统一设计的为了兼容其他不知道返回数组大小的题目这里统一使用了输出型参数来控制。第二个参数的原因也是如此。
2. 二维数组、二维数组里面的元素、需要返回二维数组里面的一维数组的元素个数这些数组都需要malloc出来。
//后台实现的地方int returnSize0;int returnColumnSizes[];
//grenerate(num,returnSize,returnColumnSizes);//函数调用
int** generate(int numRows, int* returnSize, int** returnColumnSizes)
{int** p (int**)malloc(numRows * sizeof(int*));*returnSize numRows;*returnColumnSizes (int*)malloc(sizeof(int) * numRows);for (int i 0; i numRows; i){p[i] (int*)malloc(sizeof(int) * (i 1));//给每一个二维数组的元素动态开辟一个空间(*returnColumnSizes)[i] i 1;p[i][0] p[i][i] 1;//for(int j i; j i 1; j)//条件控制有问题//for (int j 0; j i 1 ; j)for (int j 1; j i ; j){if (p[i][j]!1){p[i][j]p[i-1][j-1]p[i-1][j];}}}return p;
}3. 对于vector来讲的话动态开辟就不需要我们自己做通过resize就可以控制容器的空间大小不用malloc动态开辟了所以对于动态开辟的二维数组来讲vector实际上要简便许多。
class Solution {
public:vectorvectorint generate(int numRows) {vectorvectorint vv;vv.resize(numRows);//第二个参数不传就是匿名对象会自动调用容器中元素的构造函数。内置类型或自定义类型的构造。for(size_t i0; ivv.size(); i){vv[i].resize(i1, 0);//给每一个vectorint容器预留好空间并进行初始化vv[i][0] vv[i][vv[i].size() - 1] 1;}for(int i0; ivv.size(); i){for(int j0; jvv[i].size(); j){if(vv[i][j]0){vv[i][j]vv[i-1][j]vv[i-1][j-1];}}}return vv;}
};三、vector的元素访问操作
1.operator[]和at对于越界访问的检查机制一段经典的代码错误
1. 下面所展示的代码是比较经典的错误就是我们用reserve扩容之后就利用[]和下标来进行容器元素的访问扩容之后空间的使用权确实属于我们但是operator[]的越界访问检查机制导致了我们程序的崩溃assert(possize)所以对于元素的访问是要用resize来进行size的调整的而reserve的主要作用是用来提前预留空间在空间不够使用的情况下进行调用所以这里使用的情景有些不搭。
2. 对于at的使用所采用的越界访问检查机制是抛异常catch捕获异常之后我们可以将异常信息打印出来可以看到异常信息是无效的vector下标指的也是所传下标是无用的实际就是下标位置超过了size。 void test_vector4()
{vectorint v;v.reserve(10);//这是一段经典的错误代码。reserve改变的是capacity主要解决的是插入数据时涉及到的扩容问题。//resize改变的是size平常对于vector的容量增加还是resize多一点resize可以直接包揽reserve的活并且除此之外还能初始化空间。for (size_t i 0; i 10; i){//v[i] i;//对于[]的使用实际会有一个assert的越界断言的检查。assert(iv.size())你的下标不能超过size。//虽然reserve的确把空间开辟好了你也能用这个空间但是[]他有size和下标的越界检查所以你的程序就会报错。//reserve开空间初始化有默认值v.at(i) i;//抛异常//断言报错真正的问题是在于,release版本下面断言就失效了断言在release版本下面是不起作用的。}
}
int main()
{//test_vector1();//test_vector2();//test_vector_expand();try{//test_vector6();}catch (const exception e){//在这个地方捕获异常然后进行打印cout e.what() endl;//报错valid vector subscript无效的vector下标}//test_vector4();//test_vector5();//test_vector6();test_vector7();//string算是STL的启蒙string的源码我们就不看了return 0;
}四、vector的修改操作
1.assign和迭代器的配合使用
1. assign有两种使用方式一种是用n个value来进行容器元素的覆盖一种是用迭代器区间的元素来进行容器元素的覆盖这里的迭代器采用模板形式因为迭代器类型不仅仅可能是vector类型也有可能是其他容器类型所以这里采用模板泛型的方式。
2. 而且迭代器使用起来实际是非常方便的由于vector的底层是连续的顺序表所以我们可以通过指针±整数的方式来控制迭代器赋值的区间所以采用迭代器作为参数是非常灵活的。
void test_vector5()
{vectorint v;v.push_back(1);v.push_back(2);v.push_back(3);for (auto e : v){cout e ;}cout endl;//void assign(size_type n, const value_type val);前后类型分别为size_t和模板参数T的类型typedef那就是int类型v.assign(10, 1);for (auto e : v){cout e ;}cout endl;vectorint v1;v1.push_back(10);v1.push_back(20);v1.push_back(30);//template class InputIterator void assign(InputIterator first, InputIterator last);v.assign(v1.begin(), v1.end());for (auto e : v){cout e ;}cout endl;string str(hello world);v.assign(str.begin(), str.end());//assign里面的迭代器类型是不确定的既有可能是他自己的iterator也有可能是其他容器的迭代器类型。for (auto e : v){cout e ;}cout endl;v.assign(str.begin(), --str.end());//你可以控制迭代器的区间指定assign的容器元素内容的长度。for (auto e : v){cout e ;}cout endl;}2.insert和find的配合使用
1. 对于顺序表这种结构来说头插和头删的效率是非常低的所以vector只提供了push_back和pop_back而难免遇到头插和头删的情况时可以偶尔使用insert和erase来进行头插和头删并且insert和erase的参数都使用了迭代器类型作为参数因为迭代器更具有普适性。
2. 如果要在vector的某个位置进行插入时肯定是需要使用find接口的但其实vector的默认成员函数并没有find接口这是为什么呢因为大多数的容器都会用到查找接口也就是find所以C直接将这个接口放到算法库里面去了实现一个函数模板这个函数的实现实际也比较简单只要遍历一遍迭代器然后返回对应位置的迭代器即可所以这个函数不单独作为某个类的成员函数而是直接放到了算法库里面去。
void test_vector6()//测试insert和find
{vectorint v;v.push_back(1);v.push_back(2);v.push_back(3);v.insert(v.begin(), 4);v.insert(v.begin() 2, 4);//vector的迭代器能够直接2源自于vector的底层是连续的空间迭代器也就是连续的而list的底层不是连续空间而是一个个的节点//所以迭代器就不能来进行使用了//如果要在vector里面的数字3位置插入一个元素的话std::findfind的实现就是遍历一遍迭代器找到了就返回对应位置的迭代器。//而vector、list、deque等容器都会用到find所以find直接实现一个模板即可。vectorint::iterator it std::find(v.begin(), v.end(), 3);//string没有实现find的原因是string不仅仅要找某一个字符而且还要找一个字串所以算法库的find就不怎么适用string就自己造轮子v.insert(it, 30);for (auto e : v){cout e ;}cout endl;
}3.类外、类内、算法库的3个swap
1. vector类内的swap用于两个对象的交换在swap实现里面再调用std的swap进行内置类型的交换但C用心良苦如果你不小心使用的格式是std里面的swap格式的话也没有关系因为类外面有一个匹配vector的swap所以会优先调用类外的swapC极力不想让你调用算法库的swap就是因为如果交换的类型是自定义类型的情况下算法库的swap会进行三次深拷贝代价极大所以为了极力防止你调用算法库的swapC不仅在类内定义了swap在类外也定义了已经实例化好的swap调用时会优先调用最匹配的swap。
void test_vector8()
{vectorint v;v.push_back(1);v.push_back(2);v.push_back(3);vectorint v1;v1.swap(v);swap(v1, v);//一不小心这样用呢那也不会去调用算法库里面的三次深拷贝的swap//这里会优先匹配vector的类外成员函数既然有vector作为类型实例化出来的swap函数模板就没有必要调用算法库里面的模板进行实例化//template class T, class Alloc//void swap(vectorT, Allocx, vectorT, Allocy);
}五、看源码时需要注意的问题
1. 看源码框架的方法将类成员变量先抽出来看一看成员函数的声明具体都实现了什么功能如果想要看实现那就去.c文件抽出来具体函数去看
2. 看某些书籍时的道理和看源码是一样的要进行抽丝剥茧不要想着第一遍就把看到的所有东西都弄回如果你觉得这本书或源码非常不错你可以多次反复的去看要循序渐进的去学一段时间之后你的知识储备上来之后可能再去看书籍或者源码又有新的不同的感受所以不要想着一遍就把所有的东西都搞明白第一遍弄懂个70%-80%就很不错如果你想学扎实一点那就增加遍数。