C++初阶:STL之vector类模板

目录

一.vector的介绍及使用

1.1.vector的介绍

1.2.vector的使用

1.2.1.vector的定义

1.2.2.vector iterator的使用

1.2.3.vector的空间增长问题

1.2.4.vector的增删查改

1.3.vector在OJ中的使用

题一:只出现一次的数字

题二:杨辉三角

题三:电话号码的字母组合

1.4.vector迭代器失效问题

在VS环境下

在Linux环境下

string迭代器失效问题

二.vector深度剖析及模拟实现

2.1.原码剖析

2.2.模拟实现

2.2.1.迭代器相关

2.2.2.容量相关

2.2.3.元素访问相关

2.2.4.修改操作相关

2.2.5.成员函数相关

2.3.vector类的完整实现

vector.h

test.c


一.vector的介绍及使用

1.1.vector的介绍

1.vector是表示可变大小数组的序列容器。
2.就像数组一样,vector也采用连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
3.本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小,为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
4.vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
5.因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
6.与其它动态序列容器相比(deque, list and forward_list),vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好。

注意:

1.在向向量容器中插入元素时,如果插入操作引起了向量容量的扩展,那么执行插入之前所获得的一切迭代器,指针,引用都会失效,因为空间被重新分配了,元素的内存地址发生了变化;如果插入操作没有引起向量容量的扩展,那么只有处于插入位置之后的迭代器,指针,引用失效,因为插入位置后面的元素被移动了,对插入之前的元素不会有影响。

2.在删除向量容器中的元素时,被删除元素后面的元素会顺序向前移动,将被删除元素留下的空位补上,而后面闲置的空间并不会被释放。删除操作的效率和插入类似,被删除元素越靠前,删除操作所需的时间就越多,删除操作不会引起向量容器容量的改变,因此被删除之前的迭代器,指针,引用都能够继续使用,而删除元素之后的迭代器,指针,引用都会失效。

1.2.vector的使用

vector是STL中一种自定义数据类型,包含在头文件<vector>中,其定义如下所示:

template<class T,class Allocator=allocator<T>>
class vector;

vector是将元素置于一个动态数组中加以管理的容器,是最简单的序列式容器。它支持随机存取元素。在程序开发过程中,使用vector作为动态数组是非常方便的,尤其是在末端添加或删除元素时,速度非常快。

1.2.1.vector的定义

                       (constructor)构造函数声明               接口说明
                                vector()(重点)               无参构造
 vector(size_type n, const value_type& val = value_type())       构造并初始化n个val
                      vector(const vector& x)(重点)               拷贝构造
              vector(InputIterator first, InputIterator last)   使用迭代器进行初始化构造

详解:

说明:

迭代器是STL中最基本的一部分内容,它将容器与算法联系起来,起着粘合剂的作用,几乎STL提供的所有算法都是通过迭代器存取元素来实现的。它有些类似于指针,当参数化类型是C++内部类型时,迭代器就是指针。

注意:

vector<char> vstring s是不同的,虽然二者存放的都是字符char。对于前者来说,它是一个存放字符的数组,末尾是不存在\0的,需要我们自己手动添加;对于后者来说,它是一个字符串,而字符串的末尾默认存在\0,不需要我们自己手动添加。

案例:

void test_vector()
{//方式一:无参构造//vector对象可以初始化状态为空,不指定大小也不指定初始值vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);//for循环for (size_t i = 0; i < v1.size(); ++i){cout << v1[i] << " ";}cout << endl;//迭代器vector<int>::iterator it = v1.begin();while (it != v1.end()){cout << *it << " ";++it;}cout << endl;//范围forfor (auto e : v1){cout << e << " ";}cout << endl;//方式二:拷贝构造//用一个vector对象初始化另一个或相互赋值时,两个vector对象的元素类型必须相同//其实它调用的是vector的拷贝构造函数vector<int> copy(v1);for (auto e : copy){cout << e << " ";}cout << endl;vector<int> v = v1;for (auto e : v){cout << e << " ";}cout << endl;//方式三:构造并初始化n个val//用vector对象的大小和统一初始值来定义容器vector<int> v2(10, 1);for (auto e : v2){cout << e << " ";}cout << endl;//方式四:使用迭代器进行初始化构造//迭代器有些类似于指针,当参数化类型是C++内部类型时,迭代器就是指针vector<int> v3(v2.begin(), v2.end());for (auto e : v3){cout << e << " ";}cout << endl;//既然有了vector,为什么还需要string呢?//虽然它和string底层都是存储字符char,但还是有所不同的。//相比T是char的vector,它自身默认是不存在\0的,需要我们自己手动添加,但是对于s来说它是一个字符串,而字符串本身就是以\0结尾的。//所以说T是char的vector不能去替代string。string s1("hello world");vector<char> v4(s1.begin(), s1.end());//可以设置部分区间v3(++s1.begin(), --s1.end())for (auto e : v4){cout << e << " ";}cout << endl;
}

运行结果:

1.2.2.vector iterator的使用

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

详解:

迭代器主要有两种形式:一种是可读可写的迭代器,一种是只读的常量迭代器:

  1. 正向迭代器:容器类名::iterator 迭代器名;
  2. 常量正向迭代器:容器类名::const_iterator 迭代器名;
  3. 反向迭代器:容器类名::reverse_iterator 迭代器名;
  4. 常量反向迭代器:容器类名::const_reverse_iterator 迭代器名;

STL中的容器提供了begin()与end()函数返回一个随机访问迭代器来访问元素,除此之外,各容器还提供了rbegin()与rend()这样一对函数,用于返回一个逆向迭代器。rbegin()返回的是逆向遍历的第一个元素,即倒数第一个元素,rend()返回的是逆向遍历的末尾元素的后一个位置,即第一个元素的上一个位置,其实这个位置已经不在容器中了。

案例:

void test_vector()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);//迭代器vector<int>::iterator it = v.begin();while (it != v.end()){cout << *it << " ";++it;}cout << endl;//反向迭代器vector<int>::reverse_iterator rit = v.rbegin();//auto rit=v.rbegin();while (rit != v.rend()){cout << *rit << " ";++rit;}cout << endl;cout << v.max_size() << endl;
}

运行结果:

1.2.3.vector的空间增长问题

                   容量空间                   接口说明
                      size               获取数据个数
                    capacity               获取容量大小
                     empty              判断是否为空
                resize(重点)            改变vector的size
               reserve(重点)          改变vector的capacity

详解:

向量容器的容量与容器大小并不一定相同,vector提供了两个函数capacity()和size(),分别获取容器容量和容器大小。

reserve:改变vector的capacity

案例一:

#include<time.h>void test_vector()
{//vs下capacity是按1.5倍增长的,g++下是按2倍增长的size_t sz;vector<int> v;const size_t n = 10000;size_t begin = clock();sz = v.capacity();cout << "making v grow:\n";for (int i = 0; i < n; ++i){v.push_back(i);if (sz != v.capacity()){sz = v.capacity();cout << "capacity changed: " << sz << '\n';}}size_t end = clock();cout << "消耗时间:" << end - begin << endl;
}

运行结果:

如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够,这样就可以避免边插入边扩容导致效率低下的问题了。

案例二:

#include<time.h>void test_vector()
{//vs下capacity是按1.5倍增长的,g++下是按2倍增长的size_t sz;vector<int> v;const size_t n = 10000;size_t begin = clock();sz = v.capacity();//如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够//这样就可以避免边插入边扩容导致效率低下的问题了v.reserve(n);//提前将容量设置好,可以避免一边插入一边扩容cout << "making v grow:\n";for (int i = 0; i < n; ++i){v.push_back(i);if (sz != v.capacity()){sz = v.capacity();cout << "capacity changed: " << sz << '\n';}}size_t end = clock();cout << "消耗时间:" << end - begin << endl;
}

运行结果:

resize:改变vector的size

案例一:

void test_vector()
{//vector<int> v1;//v1.resize(10, 0);//vector<int> v2(10, 0);//reisze(size_t n, const T& data = T())//将有效元素个数设置为n个,如果增多时,增多的元素使用data进行填充//注意:resize在增多元素个数时可能会扩容vector<int> v;for (int i = 1; i < 10; i++){v.push_back(i);}v.resize(5);v.resize(8, 100);v.resize(12);cout << "v contains:" << endl;for (size_t i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;
}

运行结果:

案例二:

void test_vector()
{vector<int> v;v.reserve(10);cout << "capacity:" << v.capacity() << endl;cout << "size:" << v.size() << endl;for (size_t i = 0; i < 10; i++){v[i] = i;}cout << "v contains:" << endl;for (size_t i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;
}

运行结果:

分析:

可以发现程序运行出错,因为对于reserve而言它只是改变capacity,而不会改变size,所以此时的size依旧为0,容器中没有存取任何元素。当我们通过关系运算符operator[]去访问容器中元素时,程序就会发生异常,显示下标越界。此时,我们可以使用push_back()对程序进行修改,它在插入元素的同时,也会对size进行修改,并且也会进行一定的扩容。

改进:

小结:

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

1.2.4.vector的增删查改

      vector增删查改                                    接口说明
     push_back(重点)                                        尾插
      pop_back(重点)                                        尾删
               find  查找(注意这个是算法模块实现,不是vector的成员接口)
              insert                            在position之前插入val
              erase                           删除position位置的数据
              swap                         交换两个vector的数据空间
           operator[]                                 像数组一样访问

详解:

push_back和pop_back:从尾部插入和删除元素

push_back()函数是向vector容器末尾添加元素。先创建一个空的vector对象,再调用push_back()函数向其中添加元素。而pop_back()用于弹出vector容器末尾的一个元素。

案例:

void test_vector()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);auto it = v.begin();while (it != v.end()){cout << *it << " ";++it;}cout << endl;v.pop_back();v.pop_back();it = v.begin();while (it != v.end()){cout << *it << " ";++it;}cout << endl;
}

运行结果:

insert和erase:插入和删除元素

vector提供了一对向容器中随机插入和删除元素的函数insert()与erase()函数,其中insert()函数用于向容器中插入元素,erase()函数用于移除容器中的元素。注意:insert()函数与erase()函数中的位置只能由begin()或end()返回的迭代器来指示,而不能用纯粹的数字作为参数。

案例:

void test_vector()
{//find:查找(注意这个是算法模块实现,不是vector的成员接口)vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);for (auto e : v){cout << e << " ";}cout << endl;//1.先使用find查找2所在位置//注意:vector没有提供find方法,如果要查找只能使用STL提供的全局findvector<int>::iterator pos = find(v.begin(), v.end(), 2);if (pos != v.end()){//2.在pos位置之前插入30v.insert(pos, 20);}for (auto e : v){cout << e << " ";}cout << endl;//v.erase(pos);//err,不能直接用,因为迭代器失效了,若想要删除原来pos位置上的元素,要对迭代器重新赋值pos = find(v.begin(), v.end(), 2);if (pos != v.end()){v.erase(pos);}for (auto e : v){cout << e << " ";}cout << endl;v.erase(v.begin());//可以直接进行头删for (auto e : v){cout << e << " ";}cout << endl;
}

运行结果:

swap和operator[ ]

swap()函数主要用于交换两个vector对象的数据空间,而operator[ ]主要用于对容器中元素进行访问与修改。

案例:

void test_vector()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);//通过operator[]读写第0个位置v[0] = 10;cout << v[0] << endl;//使用for+[]方式遍历for (size_t i = 0; i < v.size(); ++i){cout << v[i] << " ";}cout << endl;vector<int> swapv;swapv.swap(v);cout << "v data:" << endl;for (size_t i = 0; i < v.size(); ++i){cout << v[i] << " ";}cout << endl;//使用迭代器遍历cout << "swapv data:" << endl;auto it = swapv.begin();while (it != swapv.end()){cout << *it << " ";++it;}//使用范围for遍历for (auto x : v){cout << x << " ";}cout << endl;
}

运行结果:

1.3.vector在OJ中的使用

题一:只出现一次的数字

题目描述:

给你一个非空整数数组nums,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。

示例 1 :

输入:nums = [2,2,1]
输出:1

示例 2 :

输入:nums = [4,1,2,1,2]
输出:4

示例 3 :

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

分析: 使用异或操作符^:相同为0,相异为1。

实现:

class Solution
{
public:int singleNumber(vector<int>& nums){int ret = 0;for (auto e : nums)ret ^= e;return ret;}
};

题二:杨辉三角

题目描述:

给定一个非负整数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,中间第[j]个数等于上一行[j-1]+[j]。

实现:

class Solution 
{
public:vector<vector<int>> generate(int numRows)//vector<vector<int>> vv;==>vv[i]对应vector<int>==>vv[i][j]对应int{vector<vector<int>> vv;vv.resize(numRows, vector<int>());//采用匿名对象对每一行进行初始化//初始化for (size_t i = 0; i < vv.size(); ++i){vv[i].resize(i + 1, 0);vv[i][0] = vv[i][vv[i].size() - 1] = 1;//将每行的第一个和最后一个元素置为1}//遍历for (size_t i = 0; i < vv.size(); ++i){for (size_t j = 0; j < vv[i].size(); ++j){if (vv[i][j] == 0){vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];}}}return vv;}
};

扩展:

vector<bit::vector<int>> vv(n):构造一个vv动态二维数组,vv中总共有n个元素,每个元素都是vector类型的,每行没有包含任何元素,如果n为5时如下所示:

vv中元素填充完成之后,如下图所示:

使用标准库中vector构建动态二维数组时与上图实际是一致的。

题三:电话号码的字母组合

题目描述:

给定一个仅包含数字2-9的字符串,返回所有它能表示的字母组合。答案可以按任意顺序返回。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

示例 2:

输入:digits = ""
输出:[]

示例 3:

输入:digits = "2"
输出:["a","b","c"]

实现:

class Solution
{string _numToStr[10] = { "", "","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz" };
public:void Combinations(const string& digits, size_t di, string combineStr, vector<string>& strV){if (di == digits.size()){strV.push_back(combineStr);return;}int num = digits[di] - '0';string str = _numToStr[num];for (auto ch : str){Combinations(digits, di + 1, combineStr + ch, strV);}}vector<string> letterCmobinations(string digits){vector<string> strV;if (digits.size() == 0){return strV;}Combinations(digits, 0, "", strV);return strV;}
};

1.4.vector迭代器失效问题

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。

情况分类:

  1. 如果容器扩容,并在其他地方重新开辟了一块空间,那么原来容器底层的空间上所保存的迭代器全部失效;

  2. 当容器调用insert()方法后,当前位置到容器末尾元素的所有迭代器全部失效;

  3. 当容器调用erase()方法后,当前位置到容器末尾元素的所有迭代器全部失效。

在VS环境下

案例一:

void test1()
{vector<int> v{ 1,2,3,4,5 };auto it = v.begin();//将有效元素个数增加到10个,多出的位置使用8填充,操作期间底层会扩容v.resize(10, 8);while (it != v.end()){cout << *it << " ";++it;}cout << endl;
}void test2()
{vector<int> v{ 1,2,3,4,5 };auto it = v.begin();//reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变v.reserve(10);while (it != v.end()){cout << *it << " ";++it;}cout << endl;
}void test3()
{vector<int> v{ 1,2,3,4,5 };auto it = v.begin();//插入元素期间,可能会引起扩容,而导致原空间被释放v.insert(v.begin(), 0);v.push_back(6);while (it != v.end()){cout << *it << " ";++it;}cout << endl;
}void test4()
{vector<int> v{ 1,2,3,4,5 };auto it = v.begin();//给vector重新赋值,可能会引起底层容量改变v.assign(10, 8);while (it != v.end()){cout << *it << " ";++it;}cout << endl;
}int main()
{test1();return 0;
}

运行结果:

分析:

会引起底层空间改变的操作,都有可能导致迭代器失效,比如:resize、reserve、insert、assign、push_back等。

  • 出错原因:以上操作都有可能导致vector扩容,也就是说vector底层原有的旧空间被释放掉,而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的旧空间,从而引起代码运行时崩溃。
  • 解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新赋值即可。

案例二:

int main()
{int a[] = { 1, 2, 3, 4 };vector<int> v(a, a + sizeof(a) / sizeof(int));//使用find查找3所在位置的iteratorvector<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就认为该位置迭代器失效了。

案例三:

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 = v.erase(it);}else{++it;}}for (auto e : v){cout << e << " ";}cout << endl;return 0;
}

运行结果:

分析:

该代码的功能是删除vector中所有的偶数,在使用erase进行删除时,出现迭代器失效问题。

  • 出错原因:因为vetor使用连续分配的内存,erase删除一个元素导致后面所有的元素都会向前移动一个位置,这些元素的地址发生了变化,所以当前位置到容器末尾元素的所有迭代器全部失效。
  • 解决方式:利用erase方法可以返回下一个有效的iterator,即:it = v.erase(it)。
在Linux环境下

注意:在Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端。

案例一:

运行结果:

分析:

扩容之后,迭代器已经失效,程序虽然可以运行,但是运行结果已经不对了。经过reserve之后,it迭代器肯定会失效,在vs下程序直接崩溃,但是在Linux下不会,虽然程序可以运行,但是输出的结果是不对的。

案例二:

运行结果:

分析:

erase删除任意位置代码后,Linux下迭代器并没有失效。因为空间还是原来的空间,后序元素往前搬移了,it的位置还是有效的。

案例三:

运行结果:

分析:

erase删除的迭代器如果是最后一个元素,删除之后it已经超过end。此时迭代器是无效的,++it导致程序崩溃。

从上述三个例子中可以看到:SGI STL中,迭代器失效后,代码并不一定会崩溃,但是运行结果肯定不对,如果it不在begin和end范围内,肯定会崩溃的。

string迭代器失效问题

与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深度剖析及模拟实现

2.1.原码剖析

迭代器:

成员变量:

图解:

2.2.模拟实现

前提:为了避免和vector类发生冲突,我们采用命名空间的方式来隔离冲突域。如下所示:

namespace bite
{template<class T>class vector {public:typedef T* iterator;typedef const T* const_iterator;//主要接口函数private:iterator _start = nullptr;iterator _finish = nullptr;iterator _end_of_storage = nullptr;};
}

2.2.1.迭代器相关

常用的迭代器分为正向迭代器与常量正向迭代器。正向迭代器可以改变对象中指定元素的值,而常量正向迭代器不可以改变对象中指定元素的值。

//非const迭代器
iterator begin()
{return _start;
}iterator end()
{return _finish;
}//const迭代器
const_iterator begin() const
{return _start;
}const_iterator end() const
{return _finish;
}

2.2.2.容量相关

size:用于获取容器大小

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

capacity:用于获取容器容量

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

empty:用于判断某个容器是否为空

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

reserve:用于改变vector的capacity

在进行reserve之前,我们要判断所传入的值 n 是否大于capacity(),若n>capacity(),则先调用new来开辟一块新的空间,然后调用memcpy函数来将旧空间的内容拷贝到新空间,接着再调用delete将旧空间进行释放,最后再更新_start,_finish以及_end_of_storage。

void reserve(size_t n)
{if (n > capacity()){//开辟新空间T* tmp = new T[n];if (_start){//拷贝数据memcpy(tmp, _start, sizeof(T) * size());//删除旧空间delete[] _start;}_start = tmp;_finish = _start + size();_end_of_storage = _start + n;}
}

调试分析:

可以发现_start_end_of_storage的值在程序执行过程中均已发生改变,而_finish的值却没有发生任何变化。因为当我们在开辟新空间tmp时,_start的值已经发生了改变(指向tmp),而此时的_finish依旧是初始值nullptr。当我们在计算_finish=_start+size()时,结合size()=_finish - _start,此时的_finish=_start+_finish-_start=_finish=nullptr。所以最终_finish的值为0x00000000。

那么该如何进行修改才能使_finish的值进行更新呢?

法一:先更新_finish,再更新_start。(不推荐)

_finish = tmp + size();
_start = tmp;
_end_of_storage = _start + n;

调试分析:

通过调试可以发现,此时的_finish的确发生了变化。但是该方法一般推荐,因为不利于代码的后期维护。

法二:先保存size()的值sz,然后再按顺序更新_start和_finish。(推荐)

void reserve(size_t n)
{if (n > capacity()){size_t sz = size();//开辟新空间T* tmp = new T[n];if (_start){//拷贝旧数据memcpy(tmp, _start, sizeof(T) * size());//释放旧空间delete[] _start;}_start = tmp;//_finish = _start + size();//err,因为size()=_finish-_start,所以_start+_finish-_start=_finish=nullptr。又因为_start已经发生变化,变成新空间的_start了,而_finish还是旧空间的_finish,,所以_finish-_start就是一个负值,再加_start就是0_finish = _start + sz;_end_of_storage = _start + n;}
}

调试分析:

通过调试可以发现,finish的确发生了变化,此时_finish为空的问题得到了有效解决。

之前vector中的内置类型均为int,当我们将其置为string类型时,案例如下:

void test_vector()
{vector<string> v;v.push_back("111111111111111111111");v.push_back("222222222222222222222");v.push_back("333333333333333333333");v.push_back("444444444444444444444");for (auto e : v){cout << e << " ";}cout << endl;v.push_back("555555555555555555555");
}

调试分析:

通过调试可以发现,当我们在调用析构函数对旧空间进行释放时,程序运行出错。结合前面所学内容,应该是出现了深浅拷贝问题。

当第五次push_back时,需要调用reserve函数进行增容,开好空间后,memcpy将旧空间的内容拷贝至新空间。由上述调试可知,memcpy完成的是浅拷贝,同时它把_start里_str所指向的空间的内容也进行了拷贝。待拷贝完成之后,将delete[] _start,而数组里存放的是string对象,它会先去调用string对象的析构函数将堆上的空间进行释放,然后再把_start所指向的空间进行释放,最后再更新_start,_finish和_end_of_storage。当程序的生命周期结束之时,vector会调用析构函数对空间进行释放,但是tmp中string对象所指向的堆空间已经被释放,此时的_str由于空间被释放已经变成一个野指针,所以vector调用析构函数时会对这个野指针进行二次释放,进而会导致程序崩溃。

小结:

  1. memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中;
  2. 如果拷贝的是自定义类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。 

结论:

如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是
浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。 

为了避免该问题发生,我们可以调用赋值运算符重载进行逐一拷贝,以此来完成深拷贝。reserve函数的完整实现如下所示:

void reserve(size_t n)
{if (n > capacity()){size_t sz = size();//开辟新空间T* tmp = new T[n];if (_start){//拷贝旧数据//memcpy(tmp, _start, sizeof(T) * size());for (size_t i = 0; i < sz; ++i){tmp[i] = _start[i];//赋值运算符重载}//释放旧空间delete[] _start;}_start = tmp;//_finish = _start + size();//err,因为size()=_finish-_start,所以_start+_finish-_start=_finish=nullptr。又因为_start已经发生变化,变成新空间的_start了,而_finish还是旧空间的_finish,,所以_finish-_start就是一个负值,再加_start就是0_finish = _start + sz;_end_of_storage = _start + n;}
}

resize:改变vector的size

resize主要用于改变vector的size大小。分三种情况进行讨论:

  1. n < _finish;
  2. _finish <= n <= _end_of_storage
  3. n >_end_of_storage
//T()采用匿名对象的方式生成一个默认缺省值
//val不能置为0,因为T不一定为int类型
void resize(size_t n, T val = T())
{if (n < size()){//删除数据_finish = _start + n;}else{if (n > capacity()){reserve(n);}while (_finish != _start + n){*_finish = val;++_finish;}}
}

2.2.3.元素访问相关

operator[ ]:主要通过下标的方式对元素进行访问。分为const版和非const版。

//非const operator[]
T& operator[](size_t pos)
{assert(pos < size());return _start[pos];
}//const operator[]
const T& operator[](size_t pos) const
{assert(pos < size());return _start[pos];
}

2.2.4.修改操作相关

push_back:尾插

void push_back(const T& x)
{if (_finish == _end_of_storage){//扩容reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;
}

pop_back:尾删

void pop_back()
{assert(!empty());--_finish;
}

insert:在pos之前插入val

在插入之前,首先要对插入位置的合法性进行判断,然后判断空间是否已满,若空间已满则进行扩容,在扩完容之后要及时更新pos,否则会发生迭代器失效问题,最后再从后向前移动元素并进行插入。

iterator insert(iterator pos, const T& val)
{assert(pos >= _start);assert(pos <= _finish);//检查容量if (_finish == _end_of_storage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;//扩容后要更新pos,解决pos失效问题}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = val;++_finish;return pos;
}

erase:删除pos位置的数据

在删除之前,首先要对删除位置的合法性进行判断,然后再从前向后移动元素并进行删除。

//返回值是一个迭代器,并且是被删除元素的下一个位置的迭代器
iterator erase(iterator pos)
{assert(pos >= _start);assert(pos < _finish);iterator start = pos + 1;while (start != _finish){*(start - 1) = *start;++start;}--_finish;return pos;
}

swap:交换两个vector的数据空间

通过调用std库中的模板函数swap,来实现两个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);
}

2.2.5.成员函数相关

构造

无参构造:

vector()
{}

有参构造:

版本一:区间构造

//若使用iterator做迭代器,会导致初始化的迭代器区间[first,last)只能是vector的迭代器
//重新声明迭代器,迭代器区间[first,last)可以是任意容器的迭代器
//[first,end)
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);++first;}
}

版本二:

vector(size_t n, const T& val = T())//const T& val = T():调用T的默认构造生成一个匿名对象,同时使用const引用可以延长匿名对象的生命周期到引用对象域结束
{reserve(n);for (size_t i = 0; i < n; ++i){push_back(val);}
}

版本三:

vector(int n, const T& val = T())//const T& val = T():调用T的默认构造生成一个匿名对象,同时使用const引用可以延长匿名对象的生命周期到引用对象域结束
{reserve(n);for (int i = 0; i < n; ++i){push_back(val);}
}

说明:

理论上将,提供了:vector(size_t n, const T& value = T())之后,vector(int n, const T& value = T())就不需要再提供了,但是对于:vector<int> v(10, 5);编译器在编译时,认为T已经被实例化为int,而10和5编译器会默认其为int类型,就不会调用:vector(size_t n, const T& value = T())这个构造方法,最终选择的是:vector(InputIterator first, InputIterator last)。因为编译器认为区间构造两个参数类型一致,因此编译器就会将InputIterator实例化为int,但是10和5根本不是一个区间,编译时就报错了,故需要增加该构造方法。

为了去调用:vector(size_t n, const T& value = T()),我们可以在传递第一个参数时加上一个u即可,那么编译器在编译时就会将其识别成无符号整数。

拷贝构造

在写拷贝构造时,要避免浅拷贝问题的发生。如果对象中涉及到资源管理,千万不要使用函数memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。

版本一:

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

版本二:

vector(const vector<T>& v)
{//reserve(v.capacity());_start = new T[v.capacity()];//如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃//memcpy(_start, v._start, sizeof(T) * v.size());//用赋值完成深拷贝for (size_t i = 0; i < v.size(); ++i){_start[i] = v._start[i];}_finish = _start + v.size();_end_of_storage = _start + v.capacity();
}

版本三:

//vector(const vector& v)//可以不加模板参数
vector(const vector<T>& v)
{//借助区间构造迭代器去完成:vector(InputIterator first, InputIterator last)vector<T> tmp(v.begin(), v.end());swap(tmp);
}

赋值运算符重载

//vector& operator=(vector v)//可以不加模板参数
vector<T>& operator=(vector<T> v)
{swap(v);return *this;
}

析构

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

2.3.vector类的完整实现

vector.h

#pragma once
#include<assert.h>namespace bite
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;//构造vector(){}//vector<int> v(10,5);vector(size_t n, const T& val = T())//const T& val = T():调用T的默认构造生成一个匿名对象,同时使用const引用可以延长匿名对象的生命周期到引用对象域结束{reserve(n);for (size_t i = 0; i < n; ++i){push_back(val);}}/*理论上将,提供了vector(size_t n, const T& value = T())之后vector(int n, const T& value = T())就不需要提供了,但是对于:vector<int> v(10, 5);编译器在编译时,认为T已经被实例化为int,而10和5编译器会默认其为int类型就不会走vector(size_t n, const T& value = T())这个构造方法,最终选择的是:vector(InputIterator first, InputIterator last)因为编译器觉得区间构造两个参数类型一致,因此编译器就会将InputIterator实例化为int但是10和5根本不是一个区间,编译时就报错了故需要增加该构造方法*/vector(int n, const T& val = T())//const T& val = T():调用T的默认构造生成一个匿名对象,同时使用const引用可以延长匿名对象的生命周期到引用对象域结束{reserve(n);for (int i = 0; i < n; ++i){push_back(val);}}//若使用iterator做迭代器,会导致初始化的迭代器区间[first,last)只能是vector的迭代器//重新声明迭代器,迭代器区间[first,last)可以是任意容器的迭代器//[first,end)template<class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}//交换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& operator=(vector v)//可以不加模板参数vector<T>& operator=(vector<T> v){swap(v);return *this;}//拷贝构造/*vector(const vector<T>& v){reserve(v.capacity());for (auto e : v){push_back(e);}}*///拷贝构造(传统写法)//vector(const vector<T>& v)//{//	//reserve(v.capacity());//	_start = new T[v.capacity()];//	//如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃//	//memcpy(_start, v._start, sizeof(T) * v.size());//	//用赋值完成深拷贝//	for (size_t i = 0; i < v.size(); ++i)//	{//		_start[i] = v._start[i];//	}//	_finish = _start + v.size();//	_end_of_storage = _start + v.capacity();//}//拷贝构造(现代写法)//vector(const vector& v)//可以不加模板参数vector(const vector<T>& v){//借助区间构造迭代器去完成:vector(InputIterator first, InputIterator last)vector<T> tmp(v.begin(), v.end());swap(tmp);}//析构~vector(){delete[] _start;_start = _finish = _end_of_storage;}//非const迭代器iterator begin(){return _start;}iterator end(){return _finish;}//const迭代器const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}//重置size//T()采用匿名对象的方式生成一个默认缺省值//val不能置为0,因为T不一定为int类型void resize(size_t n, T val = T()){if (n < size()){//删除数据_finish = _start + n;}else{if (n > capacity()){reserve(n);}while (_finish != _start + n){*_finish = val;++_finish;}}}//err//void reserve(size_t n)//{//	if (n > capacity())//	{//		T* tmp = new T[n];//		if (_start)//		{//			memcpy(tmp, _start, sizeof(T) * size());//			delete[] _start;//		}//		_start = tmp;//		_finish = _start + size();//		_end_of_storage = _start + n;//	}//}//不推荐,不利于代码维护/*void reserve(size_t n){if (n > capacity()){T* tmp = new T[n];if (_start){memcpy(tmp, _start, sizeof(T) * size());delete[] _start;}_finish = tmp + size();_start = tmp;_end_of_storage = _start + n;}}*///扩容void reserve(size_t n){if (n > capacity()){size_t sz = size();//开辟新空间T* tmp = new T[n];if (_start){//拷贝旧数据//memcpy(tmp, _start, sizeof(T) * size());for (size_t i = 0; i < sz; ++i){tmp[i] = _start[i];//赋值运算符重载}//释放旧空间delete[] _start;}_start = tmp;//_finish = _start + size();//err,因为size()=_finish-_start,所以_start+_finish-_start=_finish=nullptr。又因为_start已经发生变化,变成新空间的_start了,而_finish还是旧空间的_finish,,所以_finish-_start就是一个负值,再加_start就是0_finish = _start + sz;_end_of_storage = _start + n;}}//尾插void push_back(const T& x){if (_finish == _end_of_storage){//扩容reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;}//尾删void pop_back(){assert(!empty());--_finish;}//插入//迭代器失效:扩容引起,野指针问题iterator insert(iterator pos, const T& val){assert(pos >= _start);assert(pos <= _finish);//检查容量if (_finish == _end_of_storage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;//扩容后要更新pos,解决pos失效问题}//挪动数据iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = val;++_finish;return pos;}//删除//返回值是一个迭代器,并且是被删除元素的下一个位置的迭代器iterator erase(iterator pos){assert(pos >= _start);assert(pos < _finish);iterator start = pos + 1;while (start != _finish){*(start - 1) = *start;++start;}--_finish;return pos;}//非const operator[]T& operator[](size_t pos){assert(pos < size());return _start[pos];}//const operator[]const T& operator[](size_t pos) const{assert(pos < size());return _start[pos];}//大小size_t size() const{return _finish - _start;}//容量size_t capacity() const{return _end_of_storage - _start;}//判空bool empty(){return _start == _finish;}private:iterator _start = nullptr;iterator _finish = nullptr;iterator _end_of_storage = nullptr;};//const对象的访问void func(const vector<int>& v){//for循环for (size_t i = 0; i < v.size(); ++i){cout << v[i] << " ";}cout << endl;//迭代器vector<int>::const_iterator it = v.begin();while (it != v.end()){cout << *it << " ";++it;}cout << endl;}void test_vector1(){vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);//const对象的访问func(v1);//for循环for (size_t i = 0; i < v1.size(); ++i){cout << v1[i] << " ";}cout << endl;//迭代器vector<int>::iterator it = v1.begin();while (it != v1.end()){cout << *it << " ";++it;}cout << endl;v1.pop_back();v1.pop_back();v1.pop_back();//范围forfor (auto e : v1){cout << e << " ";}cout << endl;}//template<class T>//void f()//{//	T x = T();//	cout << x << endl;//}////void test_vector2()//{//	//内置类型有没有构造函数//	int i = int();//调用默认构造//	int j = int(1);//	//int* pi = int* ();//err//	f<int>();//	f<int*>();//	f<double>();//}void test_vector2(){vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);//for循环for (size_t i = 0; i < v1.size(); ++i){cout << v1[i] << " ";}cout << endl;cout << v1.size() << endl;cout << v1.capacity() << endl;v1.resize(10);cout << v1.size() << endl;cout << v1.capacity() << endl;//for循环for (size_t i = 0; i < v1.size(); ++i){cout << v1[i] << " ";}cout << endl;v1.resize(3);cout << v1.size() << endl;cout << v1.capacity() << endl;//for循环for (size_t i = 0; i < v1.size(); ++i){cout << v1[i] << " ";}cout << endl;}//迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了//封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的//空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)//g++没有强制检查,具体问题具体分析,结果未定义void test_vector3(){vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);//迭代器失效,要更新posv1.insert(v1.begin(), 0);func(v1);auto pos = find(v1.begin(), v1.end(), 3);if (pos != v1.end()){v1.insert(pos, 30);//pos = v1.insert(pos, 30);}func(v1);//insert以后,我们认为pos失效,不能再使用,用pos接收可以解决这个问题//(*pos)++;func(v1);}void test_vector4(){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;auto pos = find(v1.begin(), v1.end(), 3);if (pos != v1.end()){v1.erase(pos);}//erase以后,pos是否失效,答案是失效了,最好不要访问,因为行为结果未定义,跟具体编译器实现有关(*pos)++;for (auto e : v1){cout << e << " ";}cout << endl;}void test_vector5(){bite::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;//要求删除所有的偶数bite::vector<int>::iterator it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){//v1.erase(it);//errit = v1.erase(it);//返回的是删除数据的下一个位置的迭代器}else{++it;}}for (auto e : v1){cout << e << " ";}cout << endl;}void test_vector6(){vector<int> v1(10, 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;std::string s1("hello");vector<int> v3(s1.begin(), s1.end());for (auto e : v3){cout << e << " ";}cout << endl;int a[] = { 10,1,5,20,30,40,50 };vector<int> v4(a, a + 3);for (auto e : v4){cout << e << " ";}cout << endl;v1.insert(v1.begin(), 10);for (auto e : v1){cout << e << " ";}cout << endl;sort(v1.begin(), v1.end());for (auto e : v1){cout << e << " ";}cout << endl;//sort(a, a + sizeof(a) / sizeof(a[0]));//升序//greater<int> g;//sort(a, a + sizeof(a) / sizeof(a[0]), g);//降序sort(a, a + sizeof(a) / sizeof(a[0]), greater<int>());for (auto e : a){cout << e << " ";}cout << endl;}class Solution{public:vector<vector<int>> generate(int numRows)//vector<vector<int>> vv;==>vv[i]对应vector<int>==>vv[i][j]对应int{vector<vector<int>> vv;vv.resize(numRows, vector<int>());//采用匿名对象对每一行进行初始化//初始化for (size_t i = 0; i < vv.size(); ++i){vv[i].resize(i + 1, 0);vv[i][0] = vv[i][vv[i].size() - 1] = 1;//将每行的第一个和最后一个元素置为1}//遍历for (size_t i = 0; i < vv.size(); ++i){for (size_t j = 0; j < vv[i].size(); ++j){if (vv[i][j] == 0){vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];}}}return vv;}};void test_vector7(){vector<int> v1(10, 5);for (auto e : v1){cout << e << " ";}cout << endl;vector<int> v2(v1);for (auto e : v2){cout << e << " ";}cout << endl;vector<std::string> v3(3, "111111111111111111111");for (auto e : v3){cout << e << " ";}cout << endl;vector<std::string> v4(v3);for (auto e : v4){cout << e << " ";}cout << endl;v4.push_back("2222222222222222222222");v4.push_back("2222222222222222222222");v4.push_back("2222222222222222222222");for (auto e : v4){cout << e << " ";}cout << endl;//二维数组vector<vector<int>>ret = Solution().generate(5);for (size_t i = 0; i < ret.size(); ++i){for (size_t j = 0; j < ret[i].size(); ++j){cout << ret[i][j] << " ";}cout << endl;}cout << endl;}void test_vector(){vector<string> v;v.push_back("111111111111111111111");v.push_back("222222222222222222222");v.push_back("333333333333333333333");v.push_back("444444444444444444444");for (auto e : v){cout << e << " ";}cout << endl;v.push_back("555555555555555555555");}
}

test.c

#include<iostream>
#include<algorithm>
#include<functional>
#include<string>
#include<vector>
using namespace std;#include"vector.h"int main()
{//vector<int>::iterator it;//cout << typeid(it).name() << endl;bite::test_vector();return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/221078.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【vue3】处理数组方法,在数组中获取指定条件所在的数组对象等持续更新笔记~~

1、在数组中获取指定条件所在的数组对象 &#xff08;1&#xff09;filter方法获取到的是包含指定项的数组 data.checkRow res.result.filter(item > item.checked 1);打印&#xff1a; &#xff08;2&#xff09;map方法取到的是包含指定项的数组&#xff0c;如果满足…

解决PP材质粘合问题用PP专用UV胶水

PP材料已经广泛应用于各行各业&#xff0c;在粘接中会有不同的问题需求&#xff0c;那么使用专用于PP的UV胶水可能是解决PP材质粘合问题的一种有效方法。 主要在于&#xff1a;UV胶水在紫外线照射下可以快速固化&#xff0c;形成坚固的连接。所以使用PP专用UV胶水时可以考虑&am…

基于亚马逊云科技新功能:Amazon SageMaker Canvas无代码机器学习—以构建货物的交付状态检测模型实战为例深度剖析以突显其特性

授权说明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在亚马逊云科技开发者社区、 知乎、自媒体平台、第三方开发者媒体等亚马逊云科技官方渠道。 目录 &#x1f680;一. Amazon SageMaker &#x1f50e;1.1 新功能发布&#xff1a;A…

基于PCIe的NVMe学习

一&#xff1a;基本概念 1.UltraScale&#xff1a;是Xilinx ZYNQ 系列产品 2.spec&#xff1a;大家现在别纠结于具体的命令&#xff0c;了解一下就好。老板交代干活的时候&#xff0c;再找spec一个一个看吧————猜测估计是命令表之类的。 3.TLP报文部分&#xff1a; 二&…

云桌面和桌面云是什么?中国桌面云市场谁在领跑?

当我们谈论云桌面和桌面云时&#xff0c;确实很容易产生混淆。它们都涉及到云计算技术在个人计算环境中的应用&#xff0c;但具体的应用场景和侧重点有所不同。 云桌面&#xff1a;虚拟化的桌面环境 云桌面是一种基于云计算的虚拟化技术&#xff0c;它允许用户通过互联网访问一…

Visual Studio开发环境的搭建

1.引言 Visual Studio是微软公司开发的一款强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;它可以帮助开发人员进行各种编程任务&#xff0c;包括设计、开发、测试、调试和部署应用程序。典型功能包括&#xff1a; 代码编辑器。提供高效、智能的代码编辑器&#x…

Docker的私有仓库Harbor

Harbor概述 1.Harbor定义 私有&#xff0c;自定义用户的形式登陆仓库&#xff0c;拉取或者上传镜像。(内部管理的用户) Harbor 是 VMware 公司开源的企业级 Docker Registry 项目&#xff0c;其目标是帮助用户迅速搭建一个企业级的 Docker Registry 服务。 2.Docker Harbor…

【数学建模】《实战数学建模:例题与讲解》第十讲-时间序列预测(含Matlab代码)

【数学建模】《实战数学建模&#xff1a;例题与讲解》第十讲-时间序列预测&#xff08;含Matlab代码&#xff09; 基本概念移动平均&#xff08;Moving Average, MA&#xff09;:指数平滑法&#xff08;Exponential Smoothing&#xff09;:季节性调整&#xff08;Seasonal Adju…

SpringCloud系列(六)| 聊聊负载均衡

一、负载均衡概述 上一篇文章中&#xff0c;我们在集成OpenFeign的过程中提示我们需要加入了一个依赖就是&#xff1a; spring-cloud-starter-loadbalancer。 顾名思义&#xff0c;这个包的作用就是用来做负载均衡的。 简单解释一下什么是负载均衡&#xff0c;就是当我们的服…

Java8新特性:Lambda表达式

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 本…

AI 搜索将如何影响 SEO

用户可以与之交互的 AI 搜索结果是肯定的。搜索营销人员要么对此感到生气&#xff0c;什么都不做&#xff0c;要么他们可以调查即将发生的事情并做好准备。 Google SGE 和 Bing 提供了相当相似的 AI 方法&#xff0c;是传统搜索和聊天机器人的混合体。 但这并不一定是当今 AI…

HarmonyOS--基础组件Text

Text组件 可以包含Span子组件。 接口 Text(content? : string | Resource) string: Text(我是ttttt) Resource: Text($r(app.string.aaaaaa)) 先找限定词目录&#xff0c;找不到内容 找base目录 属性 除支持通用属性外&#xff0c;还支持以下属性&#xff1a; 名称 参数…

全国合作商标服大赛决赛完整规则流程

本文是全国合作商标服大赛决赛完整规则流程&#xff0c;有需要的朋友可以参考下。 一、抢答比拼 1、政策管理考核题 系统评分。抢答题共15题/条线&#xff1a;单选10题&#xff0c;多选5题&#xff0c;基础分100分 单选答对10分/答错-5分&#xff0c;多选答对20分/答错-10分…

Mozilla 推出 Solo:借助 AI 帮助零编程用户创建网站

Mozilla 近日推出名为 Solo 的全新项目&#xff0c;面向没有任何编程经验的用户&#xff0c;通过融入 AI 能力&#xff0c;所创建的网站可以媲美专业开发者的开发效果。 Mozilla 表示该项目主要针对中小型企业、个体户&#xff0c;在官方演示中&#xff0c;用户只需要输入文本、…

SAP ABAP 使用cl_md_bp_maintain=>maintain更新BP税号CN0的数据,更新结果都会变成CN5类型问题处理

SAP ABAP 使用cl_md_bp_maintain>maintain更新BP税号CN0的数据&#xff0c;更新结果都会变成CN5类型&#xff0c;CN1类型一切正常。 1、BP税号 2、跟踪方法中代码 查看底层逻辑&#xff0c;发现CN0都被强制替换成CN5了&#xff0c;BP GUI界面还能正常使用CN0. 查询NOTES&a…

QT -CloudViewer工具

QT -CloudViewer工具 一、演示效果二、关键程序三、程序下载 一、演示效果 二、关键程序 void CloudViewer::doOpen(const QStringList& filePathList) {// Open point cloud file one by onefor (int i 0; i ! filePathList.size(); i) {timeStart(); // time startmycl…

Layui继续学习

1、简单评论区代码&#xff1a; <!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>社区评论区</title> <link rel"stylesheet" href"https://cdn.staticfile.org/layui/2.6.8/css/…

1+X大数据平台运维职业技能等级证书中级

hadoop&#xff1a; 由于我的功能限制&#xff0c;我无法直接为您执行这些操作或提供实际的截图。但我可以为您提供一步步的指导&#xff0c;帮助您完成这些任务。 1. 解压JDK安装包到“/usr/local/src”路径&#xff0c;并配置环境变量。 - 解压JDK&#xff1a;tar -zxf jd…

CSS盒模型 盒子尺寸问题

什么是盒模型 CSS盒模型分为用来放置内容的content区域&#xff0c;用来调整内容和边框距离的padding区域。用来限制盒子边界的border区域以及用于调整盒子之间距离的margin区域。现在去掉margin&#xff0c;只考虑盒子内部的尺寸问题。 我们先来制作一个盒子。 <!DOCTYPE…

芸鹰蓬飞:抖店的运营技巧是什么?

抖店&#xff0c;作为抖音平台上的电商业务&#xff0c;为商家提供了一个全新的销售渠道。然而&#xff0c;要成功运营抖店&#xff0c;商家需要掌握一定的方法和技巧。下面&#xff0c;我们就来详细介绍一下抖店的运行方式。 商品选择&#xff1a;首先&#xff0c;商家需要选择…