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

C++ 模拟实现vector

目录

一、定义

二、模拟实现

1、无参初始化

2、size&capacity

3、reserve

4、push_back

5、迭代器

6、empty

7、pop_back

8、operator[ ]

9、resize

10、insert

迭代器失效问题

11、erase

12、带参初始化

13、迭代器初始化

14、析构函数

完整版代码


一、定义

本次参考SGI版本STL中的vector模拟实现。

我们可以看到上述源代码中,SGI版本vector是借助指针实现的,元素的处理是通过两个指针来实现的,而不是三个迭代器。这两个指针分别是_start和_finish。

  • _start指针指向vector中的第一个元素。
  • _finish指针指向vector中最后一个元素的下一个位置。

通过_start和_finish指针,可以确定vector中存储的元素范围。

 

此外,SGI版本的vector还使用了一个指针_end_of_storage来表示vector分配的内存空间的末尾位置。

这些指针的使用使得SGI版本的vector能够高效地进行元素的插入、删除和访问操作。

为了不影响VS中STL库已有的vector,我们选择将模拟实现的vector放在自定义命名空间中。

namespace byte
{template<class T>class vector{public:private:iterator _start;iterator _finish;iterator _end_of_storage;};
}

二、模拟实现

1、无参初始化

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

2、size&capacity

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

3、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());delete[] _start;}_start = tmp;_finish = _start + sz;_end_of_storage = _start + n;}
}
  1. if (n > capacity()):检查传入的n是否大于当前vector的容量。如果是,则需要进行内存重新分配。

  2. size_t sz = size();:保存当前vector的大小(元素个数)。

  3. T* tmp = new T[n];:创建一个新的大小为n的动态数组tmp,用于存储重新分配后的元素。

  4. if (_start):检查_start指针判断旧空间是否为非空。如果_start指针不为空,说明vector中已经有元素存储在旧的内存空间中。

  5. memcpy(tmp, _start, sizeof(T) * size());:使用memcpy函数将旧的内存空间中的元素复制到新的内存空间tmp中。这样可以保留元素的值。

  6. delete[] _start;:释放旧的内存空间。

  7. _start = tmp;:将_start指针指向新的内存空间tmp。

  8. _finish = _start + sz;:更新_finish指针,使其指向新的内存空间中的最后一个元素的下一个位置。

  9. _end_of_storage = _start + n;:更新_end_of_storage指针,使其指向新的内存空间的末尾位置。 

4、push_back

void push_back(const T& x)
{if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;
}
  1. 使用const T& x作为参数类型可以避免不必要的拷贝操作,因为传入的实参可以直接通过引用访问,而不需要进行拷贝构造。这可以提高性能和效率,特别是当处理大型对象时。

    另外,使用const T& x还可以确保传入的元素不会被修改,因为const关键字表示传入的引用是只读的,函数内部不能修改传入的对象。

  2. if (_finish == _end_of_storage) 这个条件判断用于检查当前vector是否已经达到了内存空间的末尾。如果是,则需要进行内存重新分配。

  3. reserve(capacity() == 0 ? 4 : capacity() * 2) 在需要进行内存重新分配时,调用reserve函数来预留更多的内存空间。这里使用了三目运算符,如果当前容量为0,则预留4个元素的空间,否则将当前容量乘以2来预留更多的空间。

  4. *_finish = x 将传入的元素x赋值给_finish指针所指向的位置,即在vector的末尾插入元素。

  5. ++_finish 将_finish指针向后移动一位,指向新插入元素的下一个位置,以便维护vector的边界。

5、迭代器

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;
}
  • 首先,通过typedef关键字,定义了两个迭代器类型:iteratorconst_iteratoriterator表示可修改元素的迭代器,而const_iterator表示只读元素的迭代器。
  • 然后,定义了begin()end()函数的多个重载版本,用于返回不同类型的迭代器。

6、empty

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

7、pop_back

void pop_back(const T& x)
{assert(!empty());--_finish;
}

8、operator[ ]

这个类中有两个重载的下标运算符函数,一个是非常量版本的 operator[],另一个是常量版本的 operator[]。这是为了支持对类对象的读写操作和只读操作的区分。

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

9、resize

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;}}
}

函数签名为 void resize(size_t n, T val = T()),接受两个参数:n 表示新的大小,val 表示新元素的默认值(默认为 T(),通过匿名对象T()调用类型 T 的默认构造函数)。

函数的作用是将容器的大小调整为 n。如果 n 小于当前的大小,则将容器的大小缩小为 n,丢弃多余的元素;如果 n 大于当前的大小,则在容器的末尾添加新的元素,直到容器的大小达到 n

  1. 首先,函数会检查 n 是否小于当前的大小。如果是,说明需要缩小容器的大小,将 _finish 指针移动到新的位置 _start + n,丢弃多余的元素。
  2. 如果 n 大于等于当前的大小,则需要添加新的元素。首先,函数会检查 n 是否大于容器的容量 capacity()。如果 n 大于容量,则调用 reserve 函数来增加容器的容量,以确保容器有足够的空间来存放新的元素。
  3. 然后,使用循环将新的元素 val 添加到容器的末尾,直到容器的大小达到 n。循环中,将 val 赋值给 _finish 指向的位置,然后将 _finish 指针向后移动一位。

匿名对象调用默认构造初始化。

    template<class T>void f(){T x = T();cout << x << endl;}

  • 在 resize 函数中,T val = T() 是一个带有默认值的函数参数。这里 T() 是对模板参数 T 类型的值初始化,对于内置类型,它会初始化为零(对于指针类型,初始化为 nullptr)。这和 f<T>() 模板函数中的 T x = T() 是一样的。
  • 当你调用 resize 函数时,如果你没有提供第二个参数,那么 val 就会被初始化为 T 类型的默认值。然后,resize 函数会使用 val 的值来填充新添加的元素。
  • 例如,如果你有一个 byte::vector<int> 对象 v,并调用 v.resize(10),那么 resize 函数会将 v 的大小改变为 10,并使用 int 类型的默认值 0 来填充新添加的元素。这和 f<int>() 函数打印 int 类型的默认值 0 是一样的。

 内置类型的默认初始化和直接初始化。

	void test_vector2(){// 内置类型有没有构造函数int i = int();int j = int(1);f<int>();f<int*>();f<double>();}
  • int i = int(); 使用值初始化,将 i 初始化为零。int j = int(1); 使用直接初始化,将 j 初始化为 1。

  • 分别使用 int、int* 和 double 作为模板参数调用了 f<T>() 函数。这将分别打印 int、int* 和 double 类型的默认值,即 0、nullptr 和 0。

10、insert

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,解决pos失效的问题pos = _start + len;}iterator end = _finish-1;while (end >= pos){*(end + 1) = *end;--end;}*pos = val;++_finish;return pos;
}

函数接受两个参数,第一个参数 pos 是一个迭代器,表示要插入元素的位置,第二个参数 val 是要插入的元素的值。

函数的实现分为以下几个步骤:

  1. 首先,使用 assert 断言来确保 pos 是一个有效的位置,即 pos 必须在 _start 和 _finish 之间。

  2. 然后,检查是否有足够的空间来插入新的元素。如果 _finish 等于 _end_of_storage,表示当前的内存已经用完,需要重新分配内存。这时,会调用 reserve 函数来重新分配内存,新的容量是当前容量的两倍,如果当前容量为 0,则新的容量为 4。然后,更新 pos 的值,因为重新分配内存后,原来的 pos 可能已经失效。

  3. 接下来,从 _finish-1 开始,将每个元素向后移动一位,直到 pos 的位置,为插入新的元素腾出空间。

  4. 然后,将 val 的值赋给 *pos,即在 pos 的位置插入新的元素。

  5. 最后,将 _finish 向后移动一位,表示 vector 的大小增加了一个元素。

  6. 函数返回插入新元素的位置 pos

迭代器失效问题

  1. 在 `byte::vector` 类的 `insert` 函数中,如果需要重新分配内存(即 `_finish+ + == _end_of_storage`),那么所有指向原来内存的迭代器都会失效。这是因为 `reserve` 函数会申请新的内存,复制原来的元素到新的内存,然后释放原来的内存。这个过程会导致原来的内存地址不再有效,因此所有指向原来内存的迭代器都会失效。
  2. 在这个函数中,`pos` 是一个迭代器,它指向要插入新元素的位置。如果在插入新元素之前需要重新分配内存,那么 `pos` 就会失效。为了解决这个问题,函数在重新分配内存后,会根据 `pos` 原来的位置(即 `len = pos - _start`)来更新 `pos` 的值(即 `pos = _start + len`)。这样,`pos` 就会指向新内存中相同的位置。
  3. 所以,如果你在调用 `insert` 函数之后还需要使用原来的迭代器,你需要注意迭代器可能已经失效。你可以在插入新元素后,重新获取迭代器的值。例如,如果你在插入新元素后,想要访问新元素,这里不能常量pos使用引用传值,你可以使用 `insert` 函数的返回值,它返回的是插入新元素的位置。这时外部插入元素后  (*pos)++; 可以正常运行了。

11、erase

我们先看这个版本的erase:

void erase(iterator pos)
{assert(pos >= _start && pos < _finish);iterator start = pos + 1;while (start != _finish){*(start - 1) = *start;++start;}--_finish;
}

 当我们运行以下代码程序VS会报错,linux下g++不会报错。

	void test4(){std::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(), 2);if (pos != v1.end()){v1.erase(pos);}(*pos)++;for (auto e : v1){cout << e << " ";}cout << endl;}
}

VS下: 

g++下:

这段代码中,v1.erase(pos) 会删除 vector 中的一个元素,这会导致 pos 以及所有在 pos 之后的迭代器失效。然后,代码试图通过 (*pos)++ 访问和修改已经失效的迭代器 pos,这是未定义行为,可能会导致程序崩溃或其他错误。

至于为什么 Visual Studio(VS) 会报错,而 g++ 不会报错,这主要是因为不同的编译器对未定义行为的处理方式不同。VS 的调试模式下对迭代器进行了更严格的检查,当你试图访问失效的迭代器时,它会立即报错。而 g++ 在默认设置下可能不会进行这样的检查,所以它可能不会立即报错,但这并不意味着这段代码是正确的。

下面第一种情况删除非末尾元素时,VS的报错没有意义,但在第二种情况下,VS的报错就非常有意义了。 

为了避免这种问题,你应该在删除元素后,不再使用已经失效的迭代器。如果你需要在删除元素后继续访问 vector,你应该在删除元素后重新获取迭代器的值。例如,vector::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;
}

我们来测试一下删除偶数:

void test5()
{byte::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;//要求删除所有偶数byte::vector<int>::iterator it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){it=v1.erase(it);}else{++it;}}for (auto e : v1){cout << e << " ";}cout << endl;
}

 

12、带参初始化

 一定要对_start、_finish、_out_of_storage进行初始化,不初始化默认随机值。 

vector(size_t n, const T& value = T()): _start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{reserve(n);while (n--){push_back(value);}
}

这个构造函数创建一个包含 n 个元素的 vector,每个元素都初始化为 valuevalue 参数有一个默认值,即 T(),它是 T 类型的默认构造值。

  • _start(nullptr), _finish(nullptr), _end_of_storage(nullptr): 这一行初始化三个迭代器,它们分别指向数组的开始、当前最后一个元素之后的位置,和分配的内存末端。初始化为 nullptr 表示开始时没有分配任何内存。
  • reserve(n): 这个函数调用会分配足够容纳 n 个元素的内存,但不会创建任何元素。
  • while (n--) { push_back(value); }: 这个循环会不断地添加 value 到 vector 中,直到添加了 n 个元素。push_back 函数会在 vector 的末尾添加一个新元素,并可能会增加 vector 的容量(如果需要)。

为什么对 T& 前面要加 const ?

  • 匿名对象声明周期只在当前一行,因为这行之后没人会用它了。
  • const引用会延长匿名对象的声明周期到引用对象域结束,因为以后用xx就是用匿名对象。

13、迭代器初始化

template <class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);++first;}
}

这个构造函数使用两个迭代器 first 和 last,它们分别指向输入序列的开始和结束,来初始化 vector。这个构造函数可以用于从任何可迭代的容器(如另一个 vector、列表、数组等)复制元素。

  • 在这个构造函数中,没有显式地调用 reserve 来预分配内存。这意味着每次用 push_back 时,如果当前容量不足以容纳新元素,就会自动进行内存重新分配。
  • while (first != last) { push_back(*first); ++first; }: 这个循环会遍历输入序列的每个元素,从 first 开始,一直到达 last(但不包括 last),并使用每个元素的值调用 push_back,将其添加到 vector 中。

 但是对于这句代码编译之后会报错:

vector<int> v1(10, 5);

 这是因为这段代码在vector(InputIterator first, InputIterator last)和vector(size_t n, const T& value = T())同时存在时,会优先调用前者,但调研之后在函数内部first的模板类型为int,而*first为对int类型解引用,所以这样报错了。

我们只要添加一个int类型重载函数即可解决。

vector(int n, const T& val = T())
{reserve(n);for (int i = 0; i < n; ++i){push_back(val);}
}

 这种情况在不加上上述函数可以正常使用,调用vector(size_t n, const T& value = T())。

vector<int> v1(10u, 5);

14、析构函数

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

完整版代码&测试代码

#pragma once
#include<assert.h>
namespace byte
{template<class T>class vector{public: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;}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;}}}vector():_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){}vector(size_t n, const T& value = T()): _start(nullptr), _finish(nullptr), _end_of_storage(nullptr){reserve(n);while (n--){push_back(value);}}vector(int n, const T& val = T()){reserve(n);for (int i = 0; i < n; ++i){push_back(val);}}template<class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}~vector(){delete[] _start;_start = _finish = _end_of_storage = nullptr;}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 + 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(const T& x){assert(!empty());--_finish;}void 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;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = val;++_finish;}iterator erase(iterator pos){assert(pos >= _start && pos < _finish);iterator start = pos + 1;while (start != _finish){*(start - 1) = *start;++start;}--_finish;return pos;}size_t capacity() const{return _end_of_storage - _start;}size_t size() const{return _finish - _start;}bool empty(){return _start == _finish;}T& operator[](size_t pos){assert(pos < size());return _start[pos];}const T& operator[](size_t pos) const{assert(pos < size());return _start[pos];}private:iterator _start;iterator _finish;iterator _end_of_storage;};void func(const vector<int>& v){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 << endl;}void test1(){vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);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;for (auto e : v1){cout << e << " ";}cout << endl;}void test2(){vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);cout << v1.size() << endl;cout << v1.capacity() << endl;v1.resize(10);cout << v1.size() << endl;cout << v1.capacity() << endl;func(v1);v1.resize(3);func(v1);}void test3(){std::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);//v1.push_back(5);for (auto e : v1){cout << e << " ";}cout << endl;/*v1.insert(v1.begin(), 0);for (auto e : v1){cout << e << " ";}cout << endl;*/auto pos = find(v1.begin(), v1.end(), 3);if (pos != v1.end()){//v1.insert(pos, 30);pos = v1.insert(pos, 30);}for (auto e : v1){cout << e << " ";}cout << endl;// insert以后我们认为pos失效了,不能再使用(*pos)++;for (auto e : v1){cout << e << " ";}cout << endl;}void test4(){std::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(), 2);auto pos = find(v1.begin(), v1.end(), 4);if (pos != v1.end()){v1.erase(pos);}(*pos)++;for (auto e : v1){cout << e << " ";}cout << endl;}void test5(){byte::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;//要求删除所有偶数byte::vector<int>::iterator it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){it=v1.erase(it);}else{++it;}}for (auto e : v1){cout << e << " ";}cout << endl;}void test6(){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[] = { 100, 10, 2, 20, 30 };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;}
}

相关文章:

C++ 模拟实现vector

目录 一、定义 二、模拟实现 1、无参初始化 2、size&capacity 3、reserve 4、push_back 5、迭代器 6、empty 7、pop_back 8、operator[ ] 9、resize 10、insert 迭代器失效问题 11、erase 12、带参初始化 13、迭代器初始化 14、析构函数 完整版代码 一、…...

基于hadoop下的spark安装

目录 简介 安装准备 spark安装 配置文件配置 简介 Spark主要⽤于⼤数据的并⾏计算&#xff0c;⽽Hadoop在企业主要⽤于⼤数据的存储&#xff08;⽐如HDFS、Hive和HBase 等&#xff09;&#xff0c;以及资源调度&#xff08;Yarn&#xff09;。但是也有很多公司也在使⽤MR2进…...

面试经典150题(10-13)

leetcode 150道题 计划花两个月时候刷完&#xff0c;今天&#xff08;第四天&#xff09;完成了4道(10-13)150&#xff1a; 10. &#xff08;45. 跳跃游戏 II&#xff09;题目描述&#xff1a; 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[…...

Sql server数据库数据查询

请查询学生信息表的所有记录。 答&#xff1a;查询所需的代码如下&#xff1a; USE 学生管理数据库 GO SELECT * FROM 学生信息表 执行结果如下&#xff1a; 查询学生的学号、姓名和性别。 答&#xff1a;查询所需的代码如下&#xff1a; USE 学生管理数据库 GO SELE…...

前端开发tips

前端开发tips 关于package.json里面&#xff0c;尖角号&#xff08;^&#xff09;和波浪线&#xff08;~&#xff09;的区别 在package.json里面&#xff0c;我们可以使用尖角号&#xff08;^&#xff09;和波浪线&#xff08;~&#xff09;来表示不同的包版本。这些符号通常被…...

实现跨VLAN通信、以及RIP路由协议的配置

一、如下图片&#xff1a; 1. 按照拓扑图所示&#xff0c;将8台计算机分别配置到相应的VLAN中。&#xff08;20分&#xff09; 2. 配置实现同一VLAN中的计算机可以通信。&#xff08;22分&#xff09; 3. 配置实现PC1,PC2,PC3,PC4可以互相通信&#xff0c;PC5,PC6,PC7,PC8可以互…...

使用python绘制现有彩票记录走势图

在数据分析和可视化的领域中&#xff0c;彩票走势图是一个经典的例子&#xff0c;它可以展示彩票数字随时间的出现频率和趋势。这里使用英国使用EuroMillions彩票的历史数据作为示例&#xff0c;使用Python和Matplotlib库来创建一个简单的走势图。可以在以下网站搜索.csv文件。…...

Kubernetes实战(十)-升级k8s集群

1 Kubernetes(k8s) 集群升级过程 Kubernetes 使用 kubeadm 工具来管理集群组件的升级。在集群节点层面&#xff0c;升级 Kubernetes(k8s)集群的过程可以分为以下几个步骤&#xff1a; 1&#xff09;检查当前环境和配置是否满足升级要求。 2&#xff09;升级master主节点&…...

点击el-tree小三角后去除点击后的高亮背景样式,el-tree样式修改

<div class"videoTree" v-loading"loadingTree" element-loading-text"加载中..." element-loading-spinner"el-icon-loading" element-loading-background"rgba(0, 0, 0, 0.8)" > <el-tree :default-expand-all&q…...

【电子取证篇】汽车取证数据提取与汽车取证实例浅析(附标准下载)

【电子取证篇】汽车取证数据提取与汽车取证实例浅析&#xff08;附标准下载&#xff09; 关键词&#xff1a;汽车取证&#xff0c;车速鉴定、声像资料鉴定、汽车EDR提取分析 汽车EDR一般记录车辆碰撞前后的数秒&#xff08;5s左右&#xff09;相关数据&#xff0c;包括车辆速…...

系列学习前端之第 3 章:一文精通 css

全套学习 HTMLCSSJavaScript 代码和笔记请下载网盘的资料&#xff1a; 链接: 百度网盘 请输入提取码 提取码: 6666 一、CSS基础 1. CSS简介 CSS 的全称为&#xff1a;层叠样式表 ( Cascading Style Sheets ) 。 CSS 也是一种标记语言&#xff0c;用于给 HTML 结构设…...

基于JavaWeb+SSM+Vue马拉松报名系统微信小程序的设计和实现

基于JavaWebSSMVue马拉松报名系统微信小程序的设计和实现 源码获取入口Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码获取入口 Lun文目录 1系统概述 1 1.1 研究背景 1 1.2研究目的 1 1.3系统设计思想 1 2相关技术 2 2.…...

leetcode 255.用队列实现栈

255.用队列实现栈 不出意外大概率这几天都会更新 leetcode&#xff0c;如果没有做新的题&#xff0c;大概就会把 leetcode 之前写过的题整理&#xff08;单链表的题目居多一点&#xff09;出来写成博客 今天讲的题蛮容易出错的&#xff08;注意传参啊&#xff0c;最好把队列的…...

排序算法---选择排序

1.实现流程&#xff1a; 1. 把第一个没有排序过的元素设置为最小值&#xff1b; 2. 遍历每个没有排序过的元素&#xff1b; 3. 如果元素 < 现在的最小值&#xff1b; 4. 将此元素设置成为新的最小值&#xff1b; 5. 将最小值和第一个没有排序过的位置交换 选择排序执行流程…...

物联网IC

物联网IC 电子元器件百科 文章目录 物联网IC前言一、物联网IC是什么二、物联网IC的类别三、物联网IC的应用实例四、物联网IC的作用原理总结前言 物联网IC的功能和特性可以根据不同的物联网应用需求来选择和配置,以满足物联网设备在连接、通信、感知和控制方面的需求。 一、物…...

2022年第十一届数学建模国际赛小美赛A题翼龙如何飞行解题全过程文档及程序

2022年第十一届数学建模国际赛小美赛 A题 翼龙如何飞行 原题再现&#xff1a; 翼龙是翼龙目中一个已灭绝的飞行爬行动物分支。它们存在于中生代的大部分时期&#xff1a;从三叠纪晚期到白垩纪末期。翼龙是已知最早进化出动力飞行的脊椎动物。它们的翅膀是由皮肤、肌肉和其他组…...

Blender学习--制作带骨骼动画的机器人

1. 首先创建一个机器人模型 时间关系&#xff0c;这部分步骤有时间补充 2. 然后为机器人创建一副骨架 时间关系&#xff0c;这部分步骤有时间补充 3.骨骼绑定 切换到物体模式&#xff0c;选中机器人头部&#xff0c;Shift选中骨骼&#xff0c;切换到姿态模式&#xff0c;&am…...

单片机学习13——串口通信

单片机的通信功能&#xff1a; 实现单片机和单片机的信息交换&#xff0c;实现单片机和计算机的信息交换。 计算机通信是指计算机与外部设备或计算机与计算机之间的信息交换。 通信有并行通信和串行通信两种方式。 在多微机系统以及现在测控系统中信息的交换多采用串行通信方…...

Unity 实现单例模式

目录 基本概念 饿汉模式(推荐) 懒汉模式&#xff1a; 基本概念 单例模式&#xff1a;类只有一个实例&#xff0c;一般使用static来实现单例模式&#xff1b; 比如&#xff1a;有一个Test类,实现了单例&#xff0c;假设这个唯一的实例名为SingTonle,实例在类内被实现并被stat…...

【Android12】Android Framework系列--AMS启动Activity分析

AMS启动Activity分析 通过ActivityManagerService(AMS)提供的方法&#xff0c;可以启动指定的Activity。比如Launcher中点击应用图标后&#xff0c;调用AMS的startActivity函数启动应用。 AMS提供的服务通过IActivityManager.aidl文件定义。 // frameworks/base/core/java/an…...

Hive的几种排序方式、区别,使用场景

一、几种排序和区别 Hive 支持两种主要的排序方式&#xff1a;ORDER BY 和 SORT BY。除此之外&#xff0c;还有 DISTRIBUTE BY 和 CLUSTER BY 语句&#xff0c;它们也在排序和数据分布方面发挥作用。 1. ORDER BY ORDER BY 在 Hive 中用于对查询结果进行全局排序&#xff0…...

设计模式-外观模式

设计模式专栏 模式介绍模式特点应用场景外观模式和里氏替换原则的区别代码示例Java实现外观模式python实现外观模式 外观模式在spring中的应用 模式介绍 外观模式&#xff08;Facade Pattern&#xff09;是一种结构性设计模式&#xff0c;它隐藏了系统的复杂性&#xff0c;并向…...

Kubernetes实战(九)-kubeadm安装k8s集群

1 环境准备 1.1 主机信息 iphostname10.220.43.203master10.220.43.204node1 1.2 系统信息 $ cat /etc/redhat-release Alibaba Cloud Linux (Aliyun Linux) release 2.1903 LTS (Hunting Beagle) 2 部署准备 master/与slave主机均需要设置。 2.1 设置主机名 # master h…...

【HarmonyOS开发】拖拽动画的实现

动画的原理是在一个时间段内&#xff0c;多次改变UI外观&#xff0c;由于人眼会产生视觉暂留&#xff0c;所以最终看到的就是一个“连续”的动画。UI的一次改变称为一个动画帧&#xff0c;对应一次屏幕刷新&#xff0c;而决定动画流畅度的一个重要指标就是帧率FPS&#xff08;F…...

提高问卷填写率的策略与方法

在现代社会的研究中&#xff0c;问卷调研是一种常见的数据收集方式。但是&#xff0c;随着数据的快速传播和竞争激烈的市场环境&#xff0c;怎样吸引大量的人填好问卷成为了科研人员关心的问题。本文将介绍一些方式和策略&#xff0c;以帮助你吸引大量的人填好问卷&#xff0c;…...

软件工程考试复习

第一章、软件工程概述 &#x1f31f;软件程序数据文档&#xff08;考点&#xff09; &#x1f31f;计算机程序及其说明程序的各种文档称为 &#xff08; 文件 &#xff09; 。计算任务的处理对象和处理规则的描述称为 &#xff08; 程序 &#xff09;。有关计算机程序功能、…...

PHP基础 - 类型比较

在 PHP 中,作为一种弱类型语言,它提供了松散比较和严格比较两种方式来比较变量的值和类型。 松散比较: 使用两个等号(==)进行比较,只会比较变量的值,而不会考虑它们的数据类型。例如: $a = 5; // 整数 $b = 5; // 字符串if ($a == $b) {echo "相等"; // 输…...

张正友相机标定法原理与实现

张正友相机标定法是张正友教授1998年提出的单平面棋盘格的相机标定方法。传统标定法的标定板是需要三维的,需要非常精确,这很难制作,而张正友教授提出的方法介于传统标定法和自标定法之间,但克服了传统标定法需要的高精度标定物的缺点,而仅需使用一个打印出来的棋盘格就可…...

【LeetCode】2723. 两个 Promise 对象相加

两个 Promise 对象相加 题目题解 题目 给定两个 promise 对象 promise1 和 promise2&#xff0c;返回一个新的 promise。promise1 和 promise2 都会被解析为一个数字。返回的 Promise 应该解析为这两个数字的和。 示例 1&#xff1a; 输入&#xff1a; promise1 new Promise…...

设计模式--命令模式的简单例子

引入&#xff1a;以一个对数组的增删改查为例。通过命令模式可以对数组进行增删改查以及撤销回滚。 一、基本概念 命令模式有多种分法&#xff0c;在本文中主要分为CommandMgr、Command、Receiver. CommandMgr主要用于控制命令执行等操作、Command为具体的命令、Receiver为命…...