STL容器之vector类

文章目录

  • STL容器之vector类
    • 1、vector的介绍
    • 2、vector的使用
      • 2.1、vector的常见构造
      • 2.2、vector的iterator的使用
      • 2.3、vector空间增长问题
      • 2.4、vector的增删查改
      • 2.5、vector迭代器失效问题
    • 3.vector的模拟实现

img

STL容器之vector类

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统一的迭代器和引用更好。

使用STL的三个境界:能用,明理,能扩展。


2、vector的使用

2.1、vector的常见构造

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

using namespace std;

void test_vector1() {
   vector<int> v;
   v.push_back(1);
   v.push_back(2);
   v.push_back(3);
   v.push_back(4);
   for (int e: v) {
       cout << e << " ";
   }
   cout << endl;
}

void test_vector2() {
   vector<int> v1;
   v1.push_back(1);
   v1.push_back(2);
   v1.push_back(3);
   v1.push_back(4);
   for (int e: v1) {
       cout << e << " ";
   }
   cout << endl;

   //用顺序表v1构造v2
   vector<int> v2(v1);
   for (int e: v2) {
       cout << e << " ";
   }
   cout << endl;

   //用顺序表v1的迭代区间构造v3
   vector<int> v3(v1.begin(), v1.end());
   for (int e: v3) {
       cout << e << " ";
   }
   cout << endl;

   //构造长度为10并且各元素为1的顺序表
   vector<int> v4(10, 1);
   for (int e: v4) {
       cout << e << " ";
   }
   cout << endl;
}

void test_vector3() {
   vector<int> v1;
   v1.push_back(1);
   v1.push_back(2);
   v1.push_back(3);
   v1.push_back(4);

   v1.insert(v1.begin(), 10);
   for (int e: v1) {
       cout << e << " ";
   }
   cout << endl;
   vector<int>::iterator pos = find(v1.begin(), v1.end(), 4);//find是函数模版
   v1.insert(pos, 40);
   for (int e: v1) {
       cout << e << " ";
   }

   cout << endl;
   list<int> lt;
   lt.push_back(5);
   lt.push_back(6);
   lt.push_back(7);
   lt.push_back(8);
   for (int e: lt) {
       cout << e << " ";
   }
   cout << endl;

   vector<int> v3(lt.begin(), lt.end());
   for (int e: v3) {
       cout << e << " ";
   }
   cout << endl;
   

}

//vector使用
int main() {
//    test_vector1();
//    test_vector2();
   test_vector3();
   return 0;
}

2.2、vector的iterator的使用

函数名称接口说明
begin()+end()(重点)获取第一个元素位置的iterator/const_iterator和获取最后一个元素的后一个位置的iterator/const_iterator
rbegin()+rend()获取最后一个元素位置的reverse_iterator/const_reverse_iterator和获取第一个元素的后一个位置的reverse_iterator/const_reverse_iterator
void test_vector5() {
   vector<int> v1;
   v1.push_back(1);
   v1.push_back(2);
   v1.push_back(3);
   v1.push_back(4);

   vector<int>::iterator it1 = v1.begin();
   while (it1 != v1.end()) {
       cout << *it1 << " ";
       ++it1;
   }
   cout << endl;

   vector<int>::const_iterator it2 = v1.begin();
   while (it2 != v1.end()) {
       cout << *it2 << " ";
       ++it2;
   }
   cout << endl;

}

void test_vector6() {
   vector<int> v1;
   v1.push_back(1);
   v1.push_back(2);
   v1.push_back(3);
   v1.push_back(4);

   vector<int>::reverse_iterator it1 = v1.rbegin();
   while (it1 != v1.rend()) {
       cout << *it1 << " ";
       ++it1;
   }
   cout << endl;
}

int main() {
 
   test_vector5();
   test_vector6();
 
   return 0;
}

2.3、vector空间增长问题

函数名称接口说明
size获取数据个数
resize(重点)改变vector的size
capacity获取数据的容量大小
empty判断vector是否为空
reserve(重点)改变vector的capacity
void test_vector7() {
    vector<int> v1;
    cout << v1.size() << " " << v1.capacity() << endl;

    v1.push_back(1);
    v1.push_back(2);
    v1.push_back(3);
    v1.push_back(4);

    cout << v1.size() << " " << v1.capacity() << endl;
    v1.resize(10);
    cout << v1.size() << " " << v1.capacity() << endl;
    v1.reserve(11);
    cout << v1.size() << " " << v1.capacity() << endl;
    v1.resize(5);
    cout << v1.size() << " " << v1.capacity() << endl;

    cout << v1.empty() << endl;
    v1.resize(0);
    cout << v1.empty() << endl;

}

int main() {
  
    test_vector7();
  
    return 0;
}
  • capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,CLion和g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
  • reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。
  • resize在开空间的同时还会进行初始化,影响size。
// 测试vector的默认扩容机制 -- 这里CLion是2倍扩容,VS是1.5倍扩容
void TestVectorExpand() {
    size_t sz;
    vector<int> v;
    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';
        }
    }
}

/*
	vs:运行结果:vs下使用的STL基本是按照1.5倍方式扩容
  making foo grow:
  capacity changed: 1
  capacity changed: 2
  capacity changed: 3
  capacity changed: 4
  capacity changed: 6
  capacity changed: 9
  capacity changed: 13
  capacity changed: 19
  capacity changed: 28
  capacity changed: 42
  capacity changed: 63
  capacity changed: 94
  capacity changed: 141
  
  g++运行结果:linux下使用的STL基本是按照2倍方式扩容
  making foo grow:
  capacity changed: 1
  capacity changed: 2
  capacity changed: 4
  capacity changed: 8
  capacity changed: 16
  capacity changed: 32
  capacity changed: 64
  capacity changed: 128
*/
// 如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够
// 就可以避免边插入边扩容导致效率低下的问题了
void TestVectorExpand() {
    size_t sz;
    vector<int> v;
    sz = v.capacity();
    v.reserve(100); // 提前将容量设置好,可以避免一遍插入一遍扩容
    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';
        }
    }
}

2.4、vector的增删查改

函数名称接口说明
push_back(重点)尾插
pop_back(重点)尾删
insert在position之前插入val
erase删除position位置的数据
swap交换两个vector的数据
operator[](重点)返回position位置的数据
find查找迭代区间在first到last之间的val,注意,这是函数模版
void test_vector8() {
   vector<int> v1;
   v1.push_back(1);
   v1.push_back(2);
   v1.push_back(3);
   v1.push_back(4);

   for (int e: v1) {
       cout << e << " ";
   }
   cout << endl;
   v1.pop_back();
   for (int e: v1) {
       cout << e << " ";
   }
   cout << endl;

   cout << v1[0] << endl;
   v1.insert(v1.begin(), 10);
   for (int e: v1) {
       cout << e << " ";
   }
   cout << endl;

   vector<int>::iterator it = find(v1.begin(), v1.end(), 3);
   v1.erase(it);
   for (int e: v1) {
       cout << e << " ";
   }
   cout << endl;

   cout << "-----------------------" << endl;
   vector<int> v2;
   v2.push_back(10);
   v2.push_back(20);
   v2.push_back(30);
   v2.push_back(40);
   v2.push_back(50);
   v2.push_back(60);
   for (int e: v2) {
       cout << e << " ";
   }
   cout << endl;
   cout << "-----------------------" << endl;
   
   swap(v1, v2);
   for (int e: v1) {
       cout << e << " ";
   }
   cout << endl;
   for (int e: v2) {
       cout << e << " ";
   }
   cout << endl;

}

int main() {

test_vector8();

return 0;
}

2.5、vector迭代器失效问题

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装(如list,后面会讲)

比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。解决办法是不使用insert和erase等后的原迭代器,或者使用insert和erase等后返回的迭代器。

  • 对于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();
      
          // 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容
           v.resize(100, 8);
      
          // reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
          // v.reserve(100);
      
          // 插入元素期间,可能会引起扩容,而导致原空间被释放
          // v.insert(v.begin(), 0);
          // v.push_back(8);
      
          // 给vector重新赋值,可能会引起底层容量改变
      //    v.assign(100, 8);
      
          /*
          出错原因:以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉,
         而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的
         空间,而引起代码运行时崩溃。
          解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新
         赋值即可。
          */
          
          it = v.begin();//解决办法
          while (it != v.end()) {
              cout << *it << " ";
              ++it;
          }
          cout << endl;
          return 0;
      }
      
    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; // 此处会导致非法访问,解决办法:不再访问pos
      
          v.push_back(5);
          v.push_back(6);
          v.push_back(6);
          v.push_back(5);
          v.push_back(8);
      
          for (auto e: v) {
              cout << e << " ";
          }
          cout << endl;
      
          //删除v里所有偶数
          vector<int>::iterator it = v.begin();
          while (it != v.end()) {
              if (*it % 2 == 0) {
      //            v.erase(it);//这里最好不再用it
                  it = v.erase(it);//解决办法
              } else {
                  ++it;
              }
          }
          for (auto e: v) {
              cout << e << " ";
          }
          cout << endl;
          return 0;
      }
      
    3. 与vector类似,string在插入+扩容操作+erase之后,迭代器也会失效。

      #include <string>
                
      using namespace std;
                
      int main() {
                
          string s("hello");
          string::iterator sit = s.begin();
                
          // 这里resize过后,会扩容,之前等sit的位置会改变
          s.resize(100, 'w');
                
          // 解决办法,给sit重新赋值
          sit = s.begin();
          while (sit != s.end()) {
              cout << *sit << " ";
              ++sit;
          }
          cout << endl;
                
          sit = s.begin();
          while (sit != s.end()) {
              sit = s.erase(sit);//这里s.erase(sit)后的sit位置是之前sit的后一个位置
              ++sit;//说明是隔一个元素删除
          }
                    
          for (char e: s) {
              cout << e << " ";
          }
          cout << endl;
          return 0;
      }
      

3.vector的模拟实现

  1. vector核心框架接口的模拟实现
  • vector.h文件

      //
      // Created by 徐鹏 on 2023/12/10.
      //
        
      #include <cstdio>
      #include <cstring>
      #include <list>
        
      using namespace std;
        
      #ifndef DEMO_01_VECTOR_H
      #define DEMO_01_VECTOR_H
        
      #endif //DEMO_01_VECTOR_H
        
      namespace xp {
          template<typename T>
          class vector {
          public:
              typedef T *iterator;
              typedef const T *const_iterator;
        
              vector() : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {}
        
      //        vector(const vector<T> &v) {
      //            _start = new T[v.capacity()];
      //            memcpy(_start, v._start, v.size() * sizeof(T));
      //            _finish = _start + v.size();
      //            _end_of_storage = _start + capacity();
      //        }
        
              //拷贝构造函数
              vector(const vector<T> &v) {
                  reserve(v.capacity());
                  for (const auto &e: v) {
                      push_back(e);
                  }
              }
        
              template<class InputIterator>
              //构造迭代器区间
              vector(InputIterator first, InputIterator last) {
                  while (first != last) {
                      push_back(*first);
                      ++first;
                  }
              }
        
              vector(size_t n, const T &val = T()) {
                  resize(n, val);
              }
        
              //防止和上面迭代器区间搞混
              vector(int n, const T &val = T()) {
                  resize(n, val);
              }
        
              //赋值 ,这里vector<T> v在传值的时候就已经有拷贝构造了
              vector<T> &operator=(vector<T> v) {
                  swap(v);
                  return *this;
              }
        
              T &operator[](size_t n) {
                  return *(_start + n);
              }
        
              const T &operator[](size_t n) const {
                  return *(_start + n);
              }
        
              ~vector() {
                  if (_start) {
                      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);
              }
        
              void reserve(size_t n) {
                  if (n > capacity()) {
                      //开辟新空间
                      size_t old_size = size();
                      iterator tmp = new T[n];
                      //start不为空,在复制完内容需要释放空间
                      if (_start) {
      //                    //对于自定义类型,在进行delete[] _start 之后(自定义类型里面存在指针开辟空间问题的时候),会把原来的_start里面内容进行析构和空间释放
      //                    memcpy(tmp, _start, old_size * sizeof(T));
        
                          //  解决方案,进行深拷贝
                          for (size_t i = 0; i < old_size; ++i) {
                              tmp[i] = _start[i];
                          }
                          delete[] _start;
                      }
                      _start = tmp;
                      //这里要注意,得用之前的size,不然新size使用内联进来就是_finish = _start + _finish - _start
                      _finish = _start + old_size;
                      _end_of_storage = _start + n;
                  }
              }
        
              void resize(size_t n, T val = T()) {
                  if (n > capacity()) {
                      size_t fill = _start + n - _finish;
                      reserve(n);
                      //填补之前finish后面fill个数据
                      while (fill != 0) {
                          *_finish = val;
                          _finish++;
                          fill--;
                      }
                  }
              }
        
              void push_back(const T &val) {
                  if (_finish == _end_of_storage) {
                      size_t newCapacity = capacity() == 0 ? 4 : 2 * capacity();
                      reserve(newCapacity);
                  }
                  *_finish = val;
                  ++_finish;
              }
        
              void pop_back() {
                  --_finish;
              }
        
              void insert(iterator pos, const T &val) {
                  assert(pos >= _start);
                  assert(pos <= _finish);
        
                  if (_finish == _end_of_storage) {
                      size_t len = pos - _start;
                      size_t newCapacity = capacity() == 0 ? 4 : 2 * capacity();
                      reserve(newCapacity);
                      pos = _start + len;//防止增容量后,pos位置还是之前被释放的空间
                  }
                  //memmove(pos + 1, pos, sizeof(T) * (_finish - pos)); //浅拷贝,扩容可能存在迭代器失效问题
                  iterator end = _finish - 1;
                  while (pos <= end) {
                      *(end + 1) = *end;
                      --end;
                  }
                  *pos = val;
                  ++_finish;
              }
        
              iterator begin() {
                  return _start;
              }
        
              const_iterator begin() const {
                  return _start;
              }
        
              iterator end() {
                  return _finish;
              }
        
              const_iterator end() const {
                  return _finish;
              }
    
    
              size_t size() const {
                  return _finish - _start;
              }
        
              size_t capacity() const {
                  return _end_of_storage - _start;
              }
        
          private:
              iterator _start = nullptr;
              iterator _finish = nullptr;
              iterator _end_of_storage = nullptr;
          };
            
        void test_vector1() {
            vector<char> v;
            v.reserve(10);
            cout << v.size() << " " << v.capacity() << endl;
    //        v.resize(100);
            v.resize(100, 'x');
            cout << v.size() << " " << v.capacity() << endl;
        
        }
        
        void test_vector2() {
            vector<int> v;
            v.push_back(1);
            v.push_back(2);
            v.push_back(3);
            v.push_back(4);
            v.push_back(5);
            for (auto e: v) {
                cout << e << " ";
            }
            cout << endl;
        }
        
        void test_vector3() {
            vector<string> v;
            v.push_back("hh");
            v.insert(v.begin(), "2");
            v.insert(v.begin(), "2");
            v.insert(v.begin(), "2");
            v.insert(v.begin(), "2");
            v.insert(v.begin(), "2");
            v.insert(v.begin(), "2");
            v.insert(v.begin(), "2");
            v.insert(v.begin(), "2");
            for (auto e: v) {
                cout << e << " ";
            }
            cout << endl;
        }
        
        void test_vector4() {
            vector<string> v1;
            v1.push_back("hhh");
            v1.push_back("www");
            for (auto e: v1) {
                cout << e << " ";
            }
            cout << endl;
        
            vector<string> v2;
            v2.push_back("lllll");
            v2.push_back("nnnnn");
            for (auto e: v2) {
                cout << e << " ";
            }
            cout << endl;
            cout << "----------------------" << endl;
            v1 = v2;
            for (auto e: v1) {
                cout << e << " ";
            }
            cout << endl;
            for (auto e: v2) {
                cout << e << " ";
            }
            cout << endl;
        
        }
        
        void test_vector5() {
            vector<string> v;
            v.push_back("w");
            v.push_back("s");
            v.push_back("x");
            v.push_back("p");
            cout << v[1] << endl;
        }
        
        void test_vector6() {
            vector<string> v;
            v.push_back("w");
            v.push_back("s");
            v.push_back("x");
            v.push_back("p");
            v.push_back("p");
            v.push_back("p");
            v.push_back("p");
            v.push_back("p");
            v.push_back("psdf");
            v.push_back("pdsf");
            v.push_back("pfd");
            v.push_back("pdsf");
            v.insert(v.begin(), "sdas");
            for (auto e: v) {
                cout << e << " ";
            }
            cout << endl;
        }
        
        void test_vector7() {
            vector<string> v;
            v.push_back("w");
            v.push_back("s");
            v.push_back("x");
            v.push_back("p");
            v.push_back("p");
            v.push_back("p");
            vector<string> v2(v.begin(), v.end());
            for (auto e: v2) {
                cout << e << " ";
            }
            cout << endl;
        
            int a[] = {1111, 222, 222, 3};
            vector<int> v3(a, a + 4);
            for (auto e: v3) {
                cout << e << " ";
            }
            cout << endl;
        
            list<string> lt;
            lt.push_back("hjakds");
            lt.push_back("wq");
            lt.push_back("qw");
            lt.push_back("w");
        
            vector<string> v4(lt.begin(), lt.end());
            for (auto e: v4) {
                cout << e << " ";
            }
            cout << endl;
        
            vector<int> v5(5, 5);
            for (auto e: v5) {
                cout << e << " ";
            }
            cout << endl;
        }
        
      }
    
  • main.h文件

    #include <iostream>
    #include "vector.h"
        
    //vector 模拟实现
    int main() {
    //    xp::test_vector1();
    //    xp::test_vector2();
    //    xp::test_vector3();
    //    xp::test_vector4();
    //    xp::test_vector5();
    //    xp::test_vector6();
        xp::test_vector7();
        return 0;
    }
    
  1. 使用memcpy拷贝问题

在reserve接口中存在扩容情况,对于内置类型,使用memcpy函数对数据进行拷贝不存在什么问题,但是对于自定义类型,如string类,在使用memcpy进行拷贝的时候,虽然将原数据拷贝到了新空间上,但是string类的成员变量存在指针_str,如果直接将该数据直接拷贝过来,那么新空间里的_str和旧空间的_str会指向同一块空间,在旧空间释放后,新空间的_str指针就变成了野指针。其实就是memcpy的浅拷贝问题,解决办法就是使用深拷贝,即直接把旧空间的数据采用赋值的方式(string赋值是深拷贝)放到新空间中。

  • 使用memcpy的代码

    void reserve(size_t n) {
                if (n > capacity()) {
                    //开辟新空间
                    size_t old_size = size();
                    iterator tmp = new T[n];
                    //start不为空,在复制完内容需要释放空间
                    if (_start) {
                    		memcpy(tmp, _start, old_size * sizeof(T));
                        delete[] _start;
                    }
                    _start = tmp;
                    //这里要注意,得用之前的size,不然新size使用内联进来就是_finish = _start + _finish - _start
                    _finish = _start + old_size;
                    _end_of_storage = _start + n;
                }
    }
    

    这里就导致在释放完_start后,_tmp中的_str变成野指针。

  • 解决办法:使用string的赋值进行深拷贝

    void reserve(size_t n) {
                if (n > capacity()) {
                    //开辟新空间
                    size_t old_size = size();
                    iterator tmp = new T[n];
                    //start不为空,在复制完内容需要释放空间
                    if (_start) {
    //                    //对于自定义类型,在进行delete[] _start 之后(自定义类型里面存在指针开辟空间问题的时候),会把原来的_start里面内容进行析构和空间释放
    //                    memcpy(tmp, _start, old_size * sizeof(T));
        
                        //  解决方案,进行深拷贝
                        for (size_t i = 0; i < old_size; ++i) {
                            tmp[i] = _start[i];
                        }
                        delete[] _start;
                    }
                    _start = tmp;
                    //这里要注意,得用之前的size,不然新size使用内联进来就是_finish = _start + _finish - _start
                    _finish = _start + old_size;
                    _end_of_storage = _start + n;
                }
    }
    

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


OKOK,C++ STL容器之vector类就到这里。如果你对Linux和C++也感兴趣的话,可以看看我的主页哦。下面是我的github主页,里面记录了我的学习代码和leetcode的一些题的题解,有兴趣的可以看看。

Xpccccc的github主页

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

常用网络协议的学习

TCP/IP TCP/IP的定义 TCP/IP&#xff08;Transmission Control Protocol/Internet Protocol&#xff0c;传输控制协议/互联网协议&#xff09;是互联网的基本协议&#xff0c;也是国际互联网络的基础。 TCP/IP 不是指一个协议&#xff0c;也不是 TCP 和 IP 这两个协议的合称…

天津廉租房如何申请取得廉租住房租房补贴资格

如何申请廉租住房租赁补贴资格&#xff1f; 低收入住房困难家庭应当向户籍所在地街道办事处&#xff08;乡镇人民政府&#xff09;提出申请。 申请时&#xff0c;您需要提供以下要求的原件和复印件&#xff1a; &#xff08;一&#xff09;您及家人的身份证件&#xff1b; &a…

海外代理IP干货:应该选择SOCKS55代理还是Http代理?

在使用IPFoxy全球代理时&#xff0c;选择 SOCKS55代理还是HTTP代理&#xff1f;IPFoxy代理可以SOCKS55、Http协议自主切换&#xff0c;但要怎么选择&#xff1f;为解决这个问题&#xff0c;得充分了解两种代理的工作原理和配置情况。 在这篇文章中&#xff0c;我们会简要介绍 …

每日一类:Qt GUI开发的基石《QWidget》

深入探索QWidget&#xff1a;Qt GUI开发的基石 在Qt框架中&#xff0c;QWidget类扮演着构建图形用户界面&#xff08;GUI&#xff09;的基础角色。它不仅提供了窗口的基本功能&#xff0c;还允许开发者通过继承和定制来创建各式各样的用户界面元素。本文将详细介绍QWidget的关…

低功耗运放D722,具有9MHz的高增益带宽积,转换速率为8.5V/μs

D722是低噪声、低电压、低功耗运放&#xff0c;应用广泛。D722具有9MHz的高增益带宽积&#xff0c;转换速率为8.5V/μs&#xff0c;静态电流为1.7mA&#xff08;5V电源电压&#xff09;。D722具有低电压、低噪声的特点&#xff0c;并提供轨到轨输出能力&#xff0c;D722的最大输…

vue实现自定义树形穿梭框功能

需求&#xff1a; 我们在开发过程中&#xff0c;会遇到需要将一个数据选择做成穿梭框&#xff0c;但是要求穿梭框左侧为树形结构、右侧为无层级结构的数据展示&#xff0c;ElementUI自身无法在穿梭框中添加树形结构&#xff0c;网上搜到了大佬封装的插件但是对于右侧的无树形结…

Go字符串实战操作大全!

目录 1. 引言文章结构概览 2. Go字符串基础字符串的定义与特性什么是字符串&#xff1f;Go字符串的不可变性原则 字符串的数据结构Go字符串的内部表达byte和rune的简介 3. 字符串操作与应用3.1 操作与应用字符串连接字符串切片字符串查找字符串比较字符串的替换字符串的大小写转…

androidapp开发工具,Android MVP模式详解

**工欲善其事必先利其器&#xff0c;要想拿到满意的offer&#xff0c;必须有一定的准备。**以下列出来的东西是笔者认为应该准备的东西 简历中提到的&#xff0c;一定要有准备&#xff0c;别给自己挖坑Java准备&#xff0c;Java基础&#xff0c;有的公司会扣的很细&#xff0c…

自动驾驶技术详解

&#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;自动驾驶技术 &#x1f380;CSDN主页 发狂的小花 &#x1f304;人生秘诀&#xff1a;学习的本质就是极致重复! 目录 一 自动驾驶视觉感知算法 1目标检测 1.1 两阶…

挑战30天学完Python:Day28 数据库Mysql

&#x1f389; 本系列为Python基础学习&#xff0c;原稿来源于 30-Days-Of-Python 英文项目&#xff0c;大奇主要是对其本地化翻译、逐条验证和补充&#xff0c;想通过30天完成正儿八经的系统化实践。此系列适合零基础同学&#xff0c;或仅了解Python一点知识&#xff0c;但又没…

Dsco Dropship EDI需求分析

供应商要想从Dsco处通过EDI获取订单&#xff0c;需要部署自己的EDI系统&#xff0c;与Dsco的EDI供应商CommerceHub 建立连接&#xff0c;分为两个方向&#xff1a; 1.从CommerceHub 的 Dsco 平台获取 EDI 850 采购订单 2.向Dsco发送库存&#xff08;846&#xff09;、订单状态…

中仕公考:2024年安徽省直事业单位发布公告

2024年度安徽省直事业单位统一招聘发布公告&#xff0c;具体考试时间如下&#xff1a; 报名时间&#xff1a;2024年3月5日9.00-3月11日18.00 缴费时间&#xff1a;2024年3月13日18.00前 打印准考证时间&#xff1a;2024年3月27日-3月29日 笔试时间&#xff1a;2024年3月30日…

实现前端开发几个常用技巧

如何知道iframe下载完成 定时器轮询监听readyState的状态&#xff0c;如果是 complete 或者 interactive 说明文件加载完成。 常用的全屏居中 JS 函数 JS实现deepCopy 生成星级评分 JS数组扁平化之简单方法实现 toString 优点&#xff1a;简单&#xff0c;方便&#xff0c;对…

大势智慧黄先锋:现实世界数字重建 拥抱AI 擘画自主可控的三维画卷

来源&#xff1a;中国地理信息产业协会 实景三维涉及到大面积、高精度的地理空间信息数据&#xff0c;然而早期国内99%以上的实景三维数据制作测绘单位都基于国外软件进行三维重建&#xff0c;如此重要的工作大量使用国外软件&#xff0c;如何确保国家地理空间信息的安全&#…

MAC M1 安装mongodb7.0.5 版本

1、进入官网 Download MongoDB Community Server | MongoDBDownload MongoDB Community Server non-relational database to take your next big project to a higher level!https://www.mongodb.com/try/download/community 2、选择版本 3、下载后解压 放到 /usr/local 并修改…

C语言的数据存储详解

C语言数据存储 文章目录 C语言数据存储类型的基本归类类型的意义 数据在内存中的存储整形在内存中的存储大小端整形提升和截断 浮点型在内存中的存储浮点型的存储规则E的不同情况 运用 类型的基本归类 有无符号的意义&#xff1a;生活中有写数据是没有符号之分的&#xff0c;将…

DolphinScheduler——蔚来汽车数据治理开发平台的应用改造

目录 一、业务痛点 二、应用现状 三、技术改造 3.1 稳定性 3.1.1 滚动重启黑名单机制精准路由 3.2 易用性 依赖节点优化 补数任务优化 多 SQL 执行 原文大佬的这篇基于调度系统的数据治理案例有借鉴意义&#xff0c;这里摘抄下来用作学习和知识沉淀。 一、业务痛点 蔚…

lazada、速卖通、亚马逊店铺需要补单来稳定出单率吗?

亚马逊、速卖通、Lazada、shoppe、速卖通、敦煌网、Temu、shein、阿里国际、卖家如何保证店铺出单稳定?在竞争激烈的平台上&#xff0c;保持店铺的稳定出单是每个卖家都追求的目标。为了实现这一目标&#xff0c;卖家需要综合考虑产品、运营、客户服务等多个方面的因素&#x…

刘志雄:新产品市场+新智造模式,构建“声音+”产业创新生态 | 演讲嘉宾公布

随着科技的飞速发展&#xff0c;新技术、新的应用场景不断涌现&#xff0c;也影响着“声音”产业未来的发展方向。如何应对市场变化&#xff0c;满足市场的多样化需求&#xff1f;如何应用新产品市场、智造新模式去构造“声音”产业创新生态呢&#xff1f;请到GAS2024一探究竟。…

ElasticSearch之Completion Suggester

写在前面 通过completion suggester可以实现如下的效果&#xff1a; 其实就是做的like xxx%这种。通过FST这种数据结构来存储&#xff0c;实现快速的前缀匹配&#xff0c;并且可以将es所有的数据加载到内存中所以速度completion的查询速度非常快。 需要注意&#xff0c;如果…