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

C++ STL vector基本原理和用法

文章目录

  • 基本原理
      • 1. 数据存储结构
      • 2. 内存管理机制
      • 3. 迭代器实现原理
      • 4. 元素访问原理
      • 5. 插入和删除元素原理
  • 常见用法
      • 1. 概述
      • 2. 包含头文件
      • 3. 定义和初始化
      • 4. 常用成员函数
      • 5. 迭代器
      • 6. 内存管理与性能特点
      • 7. 应用场景

基本原理

以下是关于 std::vector 的基本原理讲解:

1. 数据存储结构

std::vector 在内部通常是使用一段连续的内存空间来存储元素,这一点和普通的C风格数组类似。这种连续存储的方式使得它能够实现快速的随机访问,因为可以通过简单的指针运算(基于元素类型的大小)来定位到任意位置的元素,例如,对于一个存储 int 类型元素的 vector,如果知道首地址,要访问索引为 n 的元素,在内存中就是首地址加上 n * sizeof(int) 的偏移量。

2. 内存管理机制

  • 初始内存分配
    当创建一个 vector 时,它并不会立即分配很大的内存空间,通常会分配一个较小的初始容量(不同的标准库实现可能初始容量不同,有些可能是0,有些可能是一个较小的固定值,比如4或者8等)。例如,定义 std::vector<int> myVec; 时,它可能刚开始并没有分配实际可存储元素的内存块,只是进行了一些必要的内部状态初始化。

  • 扩容机制
    随着不断向 vector 中添加元素(通过 push_back 等操作),当元素个数达到当前容量上限时,vector 就需要进行扩容操作以容纳更多元素。扩容过程一般涉及以下几个步骤:

    • 新内存分配:它会按照一定的策略分配一块更大的连续内存空间,常见的扩容倍数是1.5倍或者2倍左右(具体倍数由不同的STL实现决定)。例如,当前 vector 的容量是4,已经存储了4个元素,当再调用 push_back 添加第5个元素时,如果按照2倍扩容策略,就会另外开辟一块能容纳8个元素的新内存空间。
    • 元素复制或移动:接着把原来内存空间中存储的所有元素复制或者移动(如果元素类型支持移动语义,在C++11及以后通常优先采用移动,以提高效率)到新分配的内存空间中。这一步涉及对每个元素的操作,如果元素类型的拷贝构造函数或者移动构造函数开销较大,那么扩容操作的整体开销也会相应增大。
    • 释放旧内存:在成功将元素迁移到新内存空间后,会释放原来的那块内存空间,以避免内存泄漏。
  • 缩容机制(可选)
    有些 vector 的实现提供了手动缩容或者在特定条件下自动缩容的机制,不过不是所有标准库都保证有自动缩容功能。比如,当调用 vectorclear 函数清空所有元素后,部分实现可能会自动释放多余的内存空间,将容量减小;也可以通过类似 shrink_to_fit 这样的函数(如果标准库支持)来显式要求 vector 释放多余内存,使其容量尽量接近当前元素个数,不过这个函数只是一个建议性的操作,具体是否能真正缩容以及缩容到何种程度还是依赖具体的实现。

3. 迭代器实现原理

vector 的迭代器本质上是一种类似指针的对象,它重载了一些必要的运算符(如 *(解引用)、++(前置和后置自增)、--(前置和后置自减)、!=(不等比较)等),使其能够像指针一样遍历 vector 中的元素。

  • 普通迭代器(beginend 等返回的迭代器)
    其内部通常就是直接持有指向 vector 中元素所在内存位置的指针。例如,begin 函数返回的迭代器指向 vector 的第一个元素所在内存地址,end 函数返回的迭代器指向最后一个元素后面的内存位置(形成左闭右开区间用于遍历等操作符合STL的通用约定)。当对迭代器进行自增操作时,就是按照元素类型的大小在内存中向后移动指针到下一个元素的位置,自减操作则相反,向前移动指针。

  • 反向迭代器(rbeginrend 等返回的迭代器)
    反向迭代器的实现相对复杂一些,它一般会对普通迭代器进行适配,内部通过一些机制来实现从后往前遍历的效果。实际上,它可以看作是对普通迭代器的一种“包装”,在自增、自减等操作的语义上进行了反转,使其能够从 vector 的末尾元素开始,向开头方向依次访问元素。

4. 元素访问原理

  • 下标访问(operator[]
    这个操作符重载的实现就是基于 vector 内部元素连续存储的特性,通过简单的指针算术运算来实现快速访问。它接收一个索引值作为参数,在内部会将这个索引值与元素类型的大小相乘(例如对于 int 类型就是乘以 sizeof(int)),然后加上首元素的地址(也就是 vector 内部管理的那块连续内存空间的起始地址),最后返回这个计算得到的内存地址对应的元素的引用,不过它不会进行边界检查,所以如果使用不当,访问越界就会导致未定义行为。

  • at 函数访问
    at 函数和 operator[] 类似也是用于访问元素,但它内部会首先进行边界检查,判断传入的索引值是否在合法的范围(即大于等于0且小于 vector 的大小)内,如果越界则会抛出 std::out_of_range 类型的异常,避免了因越界访问带来的潜在错误隐患,相对更加安全。

5. 插入和删除元素原理

  • 末尾插入(push_back
    在末尾添加元素时,如果当前 vector 还有剩余空间(即元素个数小于容量),那么直接在已有内存空间的末尾位置构造新元素即可,这个过程相对简单高效;但如果已经满容量需要扩容,就会触发上述的扩容流程后再添加元素。

  • 中间或开头插入(insert
    当要在 vector 的中间或者开头位置插入元素时,需要先将插入位置及之后的所有元素依次向后移动,为新插入的元素腾出空间,然后再在腾出的位置构造新元素。元素移动的过程同样涉及到对每个元素的拷贝或移动操作,所以在元素个数较多的 vector 中进行这类插入操作相对来说效率较低,时间复杂度是线性的 O(n),其中 nvector 的元素个数。

  • 删除元素(pop_backerase 等)
    pop_back 操作相对简单,只是销毁 vector 末尾的元素(调用其析构函数,如果有的话),然后将元素个数减1即可;而 erase 操作在删除指定位置的元素时,需要将该位置后面的所有元素依次向前移动来填补删除元素后留下的空位,同样涉及到多个元素的拷贝或移动,效率方面也会受 vector 元素个数影响,在中间或开头位置删除元素时间复杂度为 O(n)

总之,std::vector 通过巧妙的内存管理、迭代器设计以及对各种操作的底层实现,为开发者提供了一个方便、灵活且功能强大的动态数组容器,在了解其基本原理后能更好地掌握它的使用场景、性能特点以及避免一些因不当使用而可能出现的问题。 以下是对C++ STL(标准模板库)中vector的详细讲解:

常见用法

1. 概述

vector是C++ STL中非常常用的一个容器类,它提供了动态大小的数组功能,能够自动管理所存储元素的内存分配与释放,并且支持快速的随机访问。简单来说,你可以把它看作是一种可以根据需要自动扩容和缩容的数组,使用起来比普通的C风格数组更加方便、安全和灵活,广泛应用于各种需要存储和操作一组同类型元素的场景中。

2. 包含头文件

要使用vector容器,需要在代码中包含<vector>头文件,例如:

#include <vector>

3. 定义和初始化

  • 基本定义
    定义一个vector对象的语法形式通常为 std::vector<元素类型> 变量名;,这里的“元素类型”可以是C++中的各种基本数据类型(如intdoublechar等),也可以是自定义的结构体、类等类型。例如,定义一个存储整数的vector
std::vector<int> myVector;
  • 初始化方式
    • 默认初始化:像上面那样定义后,vector会被默认初始化为空,即其中不包含任何元素,其大小(通过size()成员函数获取)为0。
    • 指定初始大小和初始值:可以在定义时指定vector的初始大小以及每个元素的初始值,语法为 std::vector<元素类型> 变量名(元素个数, 初始值);。例如,创建一个包含5个初始值都为0的整数的vector
std::vector<int> anotherVector(5, 0);

此时,anotherVector的大小为5,并且每个元素的值都是0。
- 使用初始化列表初始化:利用花括号{}包裹元素来初始化vector,这种方式在C++11及以后的版本中更加常用和方便。例如:

std::vector<int> initListVector = {1, 2, 3};

这个vector包含了3个元素,分别是1、2和3。
- 从其他vector复制或移动初始化:可以通过已有的vector对象来初始化一个新的vector,实现复制或移动语义(如果适用)。例如:

std::vector<int> sourceVector = {1, 2, 3};
// 复制初始化
std::vector<int> copiedVector(sourceVector);
// 移动初始化(假设C++11及以上,利用std::move函数实现移动语义)
std::vector<int> movedVector(std::move(sourceVector));

复制初始化会创建一个新的vector,其元素与源vector完全相同;而移动初始化则是将源vector的资源(如内存空间等)转移给新的vector,源vector在移动后通常处于一种有效但已转移资源的特殊状态(比如大小变为0等)。

4. 常用成员函数

  • 添加和删除元素相关函数
    • push_back():用于在vector的末尾添加一个元素。例如:
std::vector<int> numbers;
numbers.push_back(5);  // 在末尾添加整数5
numbers.push_back(10); // 再添加整数10,此时numbers包含两个元素,分别是5和10
- **`pop_back()`**:与`push_back()`相反,它用于删除`vector`末尾的一个元素。例如:
std::vector<int> nums = {1, 2, 3};
nums.pop_back();  // 删除末尾的元素3,此时nums包含元素1和2
- **`insert()`**:可以在指定位置插入一个或多个元素。它有多种重载形式,常见的一种是 `iterator insert(iterator position, const T& value);`,其中`position`是指向插入位置的迭代器(可以通过`begin()`、`end()`等函数获取迭代器来指定位置),`value`是要插入的元素值。例如:
std::vector<int> vec = {1, 3};
auto it = vec.begin() + 1;  // 指向索引为1的位置(也就是元素3所在位置)
vec.insert(it, 2);  // 在索引为1的位置插入元素2,此时vec变为{1, 2, 3}

还可以一次插入多个相同元素,比如 iterator insert(iterator position, size_type n, const T& value); 形式可以在指定位置插入n个相同的value元素。
- erase():用于删除指定位置的一个或多个元素,同样有多种重载形式。例如,删除单个元素可以这样用:iterator erase(iterator position);,删除一段元素区间可以用 iterator erase(iterator first, iterator last);,其中firstlast分别是要删除区间的起始和结束迭代器(左闭右开区间)。例如:

std::vector<int> v = {1, 2, 3, 4};
auto it = v.begin() + 1;  // 指向元素2
v.erase(it);  // 删除元素2,此时v变为{1, 3, 4}

删除一段元素示例:

std::vector<int> ve = {1, 2, 3, 4, 5};
auto start = ve.begin() + 1;  // 指向元素2
auto end = ve.begin() + 3;  // 指向元素4后面的位置(左闭右开区间概念)
ve.erase(start, end);  // 删除元素2和3,此时ve变为{1, 4, 5}
  • 访问元素相关函数
    • at():用于安全地访问vector中的元素,它会进行边界检查,如果访问越界会抛出 std::out_of_range 异常。语法为 reference at(size_type n);,其中n是要访问元素的索引(从0开始计数)。例如:
std::vector<int> values = {10, 20, 30};
try {int element = values.at(1);  // 获取索引为1的元素,即20std::cout << element << std::endl;
} catch (const std::out_of_range& e) {std::cerr << "访问越界: " << e.what() << std::endl;
}
- **`operator[]`(下标运算符)**:也用于访问元素,它与数组的下标访问方式类似,但不会进行边界检查,如果访问越界会导致未定义行为,所以使用时要确保索引在合法范围内。例如:
std::vector<int> data = {5, 10};
int element = data[0];  // 获取第一个元素5,注意这里没有边界检查,需谨慎使用
- **`front()`**:返回`vector`的第一个元素的引用,可以用于获取或修改第一个元素的值。例如:
std::vector<int> firstVector = {1, 2};
int firstElement = firstVector.front();  // 获取第一个元素1
firstVector.front() = 10;  // 将第一个元素修改为10,此时firstVector变为{10, 2}
- **`back()`**:与`front()`相对,返回`vector`的最后一个元素的引用,同样可用于获取或修改最后一个元素的值。例如:
std::vector<int> lastVector = {3, 4};
int lastElement = lastVector.back();  // 获取最后一个元素4
lastVector.back() = 40;  // 将最后一个元素修改为40,此时lastVector变为{3, 40}
  • 获取容器信息相关函数
    • size():返回vector中当前元素的个数,返回值类型是 std::vector<元素类型>::size_type(通常是无符号整数类型)。例如:
std::vector<int> sizeVector = {1, 2, 3};
std::vector<int>::size_type elementCount = sizeVector.size();  // 获取元素个数,这里elementCount的值为3
- **`capacity()`**:返回`vector`当前已经分配的内存空间能够容纳的元素个数,也就是它的容量。一般来说,随着不断向`vector`中添加元素,当元素个数接近容量时,`vector`会自动进行扩容操作来分配更多的内存空间。例如:
std::vector<int> capVector;
std::cout << "初始容量: " << capVector.capacity() << std::endl;  // 初始容量可能为0或者一个较小的值,取决于实现
capVector.push_back(1);
capVector.push_back(2);
std::cout << "添加元素后的容量: " << capVector.capacity() << std::endl;  // 容量可能已经自动扩容了
- **`empty()`**:判断`vector`是否为空,如果为空(即元素个数为0)则返回`true`,否则返回`false`。例如:
std::vector<int> emptyVector;
if (emptyVector.empty()) {std::cout << "该vector为空" << std::endl;
}
std::vector<int> nonEmptyVector = {1};
if (!nonEmptyVector.empty()) {std::cout << "该vector不为空" << std::endl;
}
  • 其他常用函数
    • clear():用于清空vector中的所有元素,将其大小变为0,但不会释放已分配的内存空间(容量可能不会改变,除非后续有添加或删除元素等操作触发了内存重新分配)。例如:
std::vector<int> clearVector = {1, 2, 3};
clearVector.clear();
if (clearVector.empty()) {std::cout << "已清空vector,现在为空" << std::endl;
}
- **`resize()`**:可以改变`vector`的大小,如果新大小大于当前大小,会根据元素类型的默认构造函数(对于内置类型可能会进行未定义初始化,对于类类型会调用默认构造函数)或者指定的初始值来填充新增的元素;如果新大小小于当前大小,则会删除多余的元素。例如:
std::vector<int> resizeVector(3, 1);  // 初始化为{1, 1, 1}
resizeVector.resize(5, 0);  // 扩大为{1, 1, 1, 0, 0},新增元素初始化为0
resizeVector.resize(2);  // 缩小为{1, 1},删除后面的元素

5. 迭代器

vector支持迭代器来遍历容器中的元素,迭代器就像是指向容器中元素的指针,可以通过它来依次访问每个元素。常用的迭代器相关操作有:

  • begin()end()begin()返回指向vector第一个元素的迭代器,end()返回指向vector最后一个元素后面位置的迭代器(形成一个左闭右开的区间概念,用于循环遍历等操作符合STL的通用设计模式)。例如,使用迭代器遍历vector的常见方式如下:
std::vector<int> iterVector = {1, 2, 3};
for (auto it = iterVector.begin(); it!= iterVector.end(); ++it) {std::cout << *it << " ";  // 输出每个元素,*it用于解引用迭代器获取元素值
}
// 输出: 1 2 3
  • rbegin()rend():这两个函数分别返回反向迭代器,用于从后往前遍历vectorrbegin()指向最后一个元素,rend()指向第一个元素前面的位置(同样是左闭右开区间概念)。例如:
std::vector<int> reverseIterVector = {4, 5, 6};
for (auto rit = reverseIterVector.rbegin(); rit!= reverseIterVector.rend(); ++rit) {std::cout << *rit << " ";  // 从后往前输出元素,这里输出: 6 5 4
}

6. 内存管理与性能特点

  • 内存自动管理vector会自动处理内存的分配和释放,当添加元素导致当前分配的内存空间不足时,它会自动进行扩容操作,通常是按照一定的倍数(不同的STL实现可能有不同的扩容策略,常见的是1.5倍或2倍等)分配新的更大的内存空间,然后将原有元素复制或移动到新空间中,并释放原来的内存。这种自动管理机制方便了开发者,但也可能在频繁扩容时带来一定的性能开销(尤其是元素类型的拷贝或移动成本较高时)。
  • 随机访问性能好:由于vector内部元素在内存中是连续存储的,类似于普通的数组,所以它支持快速的随机访问,通过下标运算符或者迭代器解引用访问元素的时间复杂度为常数时间 O(1)
  • 插入和删除元素的性能特点:在vector末尾添加或删除元素(通过push_back()pop_back())的操作通常比较高效,时间复杂度接近常数时间(平均情况,不考虑偶尔的扩容操作带来的影响);但在中间或者开头位置插入或删除元素(通过insert()erase())可能相对较慢,因为需要移动插入或删除位置后面的所有元素,时间复杂度为线性时间 O(n),其中nvector中元素的个数。

7. 应用场景

  • 存储一组同类型的数据:比如存储游戏中的玩家分数列表、学生的成绩数组等,方便进行数据的添加、删除、修改以及遍历统计等操作。
  • 作为函数参数传递数组:在函数间传递数组时,如果数组大小不确定或者可能会变化,使用vector作为参数传递要比传递普通C风格数组更加方便、安全,避免了传递数组长度等额外参数以及数组越界等风险。例如:
void processVector(const std::vector<int>& vec) {// 在这里可以安全地遍历vec等操作,不用担心越界问题for (int element : vec) {std::cout << element << " ";}
}
int main() {std::vector<int> data = {1, 2, 3};processVector(data);return 0;
}
  • 配合其他STL算法使用vector可以很好地与STL中的各种算法(如排序算法std::sort、查找算法std::find等)配合使用,实现更复杂的数据处理功能。例如:
#include <algorithm>
std::vector<int> numbers = {5, 3, 1, 4, 2};
std::sort(numbers.begin(), numbers.end());  // 对vector中的元素进行排序

总之,vector是C++ STL中一个功能强大、使用方便的容器,掌握它的各种特性和使用方法对于高效地进行C++编程非常有帮助,能够满足很多常见的动态数组相关的编程需求。

相关文章:

C++ STL vector基本原理和用法

文章目录 基本原理1. 数据存储结构2. 内存管理机制3. 迭代器实现原理4. 元素访问原理5. 插入和删除元素原理 常见用法1. 概述2. 包含头文件3. 定义和初始化4. 常用成员函数5. 迭代器6. 内存管理与性能特点7. 应用场景 基本原理 以下是关于 std::vector 的基本原理讲解&#xf…...

【计算机视觉基础CV-图像分类】05 - 深入解析ResNet与GoogLeNet:从基础理论到实际应用

引言 在上一篇文章中&#xff0c;我们详细介绍了ResNet与GoogLeNet的网络结构、设计理念及其在图像分类中的应用。本文将继续深入探讨如何在实际项目中应用这些模型&#xff0c;特别是如何保存训练好的模型、加载模型以及使用模型进行新图像的预测。通过这些步骤&#xff0c;读…...

【人工智能-初级】基于用户的协同过滤推荐算法

文章目录 1. 数据集2. 实验代码3. 代码解释4. 实验结果5. 评估基于用户的协同过滤算法是一种常见的推荐算法,它的核心思想是根据用户之间的相似性来进行推荐。 实验案例: 使用的是电影推荐数据集 MovieLens,实验中我们会通过用户评分数据计算用户之间的相似性,并使用基于用户…...

如何识别钓鱼邮件和诈骗网站?(附网络安全意识培训PPT资料)

识别钓鱼邮件和诈骗网站是网络安全中的一个重要环节。以下是一些识别钓鱼邮件和诈骗网站的方法&#xff1a; 识别钓鱼邮件&#xff1a; 检查发件人地址&#xff1a; 仔细查看发件人的电子邮件地址&#xff0c;看是否与官方域名一致。 检查邮件内容&#xff1a; 留意邮件中是否…...

Rust 在前端基建中的使用

摘要 随着前端技术的不断发展&#xff0c;前端基础设施&#xff08;前端基建&#xff09;的建设已成为提升开发效率、保障产品质量的关键环节。然而&#xff0c;在应对复杂业务场景与高性能需求时&#xff0c;传统的前端技术栈逐渐暴露出诸多不足。近年来&#xff0c;Rust语言…...

【人工智能】基于Python和OpenCV实现实时人脸识别系统:从基础到应用

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 随着人工智能和计算机视觉的快速发展,人脸识别技术已广泛应用于监控、安全、社交媒体、金融和医疗等领域。本文将介绍如何利用Python和Ope…...

Python 自动化 打开网站 填表登陆 例子

图样 简价&#xff1a; 简要说明这个程序的功能&#xff1a; 1. **基本功能**&#xff1a; - 自动打开网站 - 自动填写登录信息&#xff08;号、公司名称、密码&#xff09; - 显示半透明状态窗口实时提示操作进度 2. **操作流程**&#xff1a; - 打开网站后自动…...

【Chrome】浏览器提示警告Chrome is moving towards a new experience

文章目录 前言一、如何去掉 前言 Chrome is moving towards a new experience that allows users to choose to browse without third-party cookies. 这是谷歌浏览器&#xff08;Chrome&#xff09;关于隐私策略更新相关的提示 提示&#xff1a;以下是本篇文章正文内容&…...

网络下载ts流媒体

网络下载ts流媒体 查看下载排序合并 很多视频网站&#xff0c;尤其是微信小程序中的长视频无法获取到准确视频地址&#xff0c;只能抓取到.ts片段地址&#xff0c;下载后发现基本都是5~8秒时长。 例如&#xff1a; 我们需要将以上地址片段全部下载后排序后再合成新的长视频。 …...

iDP3复现代码模型训练全流程(一)——train_policy.sh

iDP3 核心脚本包括三个&#xff1a;deploy_policy.sh、vis_dataset.sh、train_policy.sh&#xff0c;分别代表了部署、预处理和训练&#xff0c;分别作为对应 py 脚本的参数设置前置环节 训练环节仅需运行指令&#xff1a; # 3d policy bash scripts/train_policy.sh idp3 gr1…...

重温设计模式--单例模式

文章目录 单例模式&#xff08;Singleton Pattern&#xff09;概述单例模式的实现方式及代码示例1. 饿汉式单例&#xff08;在程序启动时就创建实例&#xff09;2. 懒汉式单例&#xff08;在第一次使用时才创建实例&#xff09; 单例模式的注意事项应用场景 C代码懒汉模式-经典…...

【人工智能】Python中的机器学习管道:如何用scikit-learn构建高效的ML管道

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 在机器学习项目中,数据预处理、特征工程、模型训练与评估是不可或缺的环节。随着项目规模的扩大和复杂度的增加,手动管理这些步骤不仅繁琐…...

Redis存在安全漏洞

Redis是美国Redis公司的一套开源的使用ANSI C编写、支持网络、可基于内存亦可持久化的日志型、键值&#xff08;Key-Value&#xff09;存储数据库&#xff0c;并提供多种语言的API。 Redis存在安全漏洞。攻击者利用该漏洞使用特制的Lua脚本触发堆栈缓冲区溢出漏洞&#xff0c;从…...

Scala图书管理系统

项目创建并实现基础UI package org.appimport scala.io.StdInobject Main {def main(args: Array[String]): Unit {var running truewhile (running) {println("欢迎来到我的图书管理系统&#xff0c;请选择")println("1.查看所有图书")println("2…...

【数据可视化案列】白葡萄酒质量数据的EDA可视化分析

&#x1f9d1; 博主简介&#xff1a;曾任某智慧城市类企业算法总监&#xff0c;目前在美国市场的物流公司从事高级算法工程师一职&#xff0c;深耕人工智能领域&#xff0c;精通python数据挖掘、可视化、机器学习等&#xff0c;发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…...

Postman接口测试:全局变量/接口关联/加密/解密

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 全局变量和环境变量 全局变量&#xff1a;在postman全局生效的变量&#xff0c;全局唯一 环境变量&#xff1a;在特定环境下生效的变量&#xff0c;本环境内唯一 …...

vue+elementui实现下拉表格多选+搜索+分页+回显+全选2.0

一、vueelementui实现下拉表格多选搜索1.0 二、vueelementui实现下拉表格多选搜索分页回显全选2.0 在1.0的基础上&#xff0c;终于可以实现在下拉框表格分页的前提下不同页码的回显辣&#xff0c;分页是前端来分页的&#xff08;代码略乱且没有封装还很长&#xff0c;随便看看…...

电商系统-产品经理

电视产品经理的工作体系&#xff1a; 产品经理的分类与职责 C端产品经理&#xff1a;面向个人用户&#xff0c;关注用户体验和产品易用性B端产品经理&#xff1a;面向企业客户&#xff0c;注重功能完整性和商业价值专业方向细分&#xff1a; 用户产品经理&#xff1a;专注用户…...

《庐山派从入门到...》PWM板载蜂鸣器

《庐山派从入门到...》PWM板载蜂鸣器 配置PWM模块控制板载无源蜂鸣器播放【一闪一闪亮晶晶】播放do re mi 《庐山派从入门到...》PWM控制无源蜂鸣器 PWM&#xff08;Pulse Width Modulation&#xff0c;脉宽调制&#xff09;是一种在嵌入式系统中常用的技术&#xff0c;它可以用…...

【河南新标】豫财预〔2024〕105号-《关于省级政务信息化建设项目支出预算标准的规定》-费用标准解读系列29

2024年12月3日&#xff0c;河南省财政厅发布了《关于省级政务信息化建设项目支出预算标准的规定》豫财预〔2024〕105号。《关于省级政务信息化建设项目支出预算标准的规定 &#xff08;试行&#xff09;》&#xff08;豫财预 〔2020〕81号&#xff09;同时废止。新的豫财预〔20…...

第19节 Node.js Express 框架

Express 是一个为Node.js设计的web开发框架&#xff0c;它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用&#xff0c;和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

Leetcode 3576. Transform Array to All Equal Elements

Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接&#xff1a;3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到&#xf…...

简易版抽奖活动的设计技术方案

1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?

Golang 面试经典题&#xff1a;map 的 key 可以是什么类型&#xff1f;哪些不可以&#xff1f; 在 Golang 的面试中&#xff0c;map 类型的使用是一个常见的考点&#xff0c;其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建

制造业采购供应链管理是企业运营的核心环节&#xff0c;供应链协同管理在供应链上下游企业之间建立紧密的合作关系&#xff0c;通过信息共享、资源整合、业务协同等方式&#xff0c;实现供应链的全面管理和优化&#xff0c;提高供应链的效率和透明度&#xff0c;降低供应链的成…...

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施&#xff0c;由雇主和个人按一定比例缴纳保险费&#xff0c;建立社会医疗保险基金&#xff0c;支付雇员医疗费用的一种医疗保险制度&#xff0c; 它是促进社会文明和进步的…...

React Native在HarmonyOS 5.0阅读类应用开发中的实践

一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强&#xff0c;React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 &#xff08;1&#xff09;使用React Native…...

vue3 定时器-定义全局方法 vue+ts

1.创建ts文件 路径&#xff1a;src/utils/timer.ts 完整代码&#xff1a; import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“&#x1f916;手搓TuyaAI语音指令 &#x1f60d;秒变表情包大师&#xff0c;让萌系Otto机器人&#x1f525;玩出智能新花样&#xff01;开整&#xff01;” &#x1f916; Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制&#xff08;TuyaAI…...

数据库分批入库

今天在工作中&#xff0c;遇到一个问题&#xff0c;就是分批查询的时候&#xff0c;由于批次过大导致出现了一些问题&#xff0c;一下是问题描述和解决方案&#xff1a; 示例&#xff1a; // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...