Vector的学习

vector简介

vector的相关文档对于想深入了解的同学可以参考这个文档进行学习。

  1. vector是表示可变大小数组的序列容器。

  2. 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。

  3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。

  4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。

  5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。

  6. 与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好。

vector的简单说明

vector学习时一定要学会查看文档,vector在实际中非常的重要,在实际中我们熟悉常见的接口并学会使用就可以。下面我将介绍一些常用的接口,大多数的使用跟string一样。
注意:vector的使用要包含头文件include<vector>

vector的定义

请添加图片描述

1.默认构造函数:
vector()

  • 解释:构造一个没有元素的空容器。
  • 示例:
#include<iostream>
#include<vector>
using namespace std;int main()
{vector<int> v;  //构造一个空的int的vectorreturn 0;
}

2.有初始值的构造函数
vector(size_type n, const value_type& val = value_type())

  • 解释:构造一个n个大小空间的vector,并且每个值都初始化成val
  • 示例:
vector<int> v(5, 2);

上述示例,创建了一个包含5个元素,并且每个值都是2的整形类型的vector。


3.范围构造函数

template <class InputIterator>
vector (InputIterator first, InputIterator last,const allocator_type& alloc = allocator_type());
  • 解释:构造一个容器,其元素数与范围 [first,last) 一样多,每个元素按相同的顺序从该范围中的相应元素构造
  • 示例:
void test_vector1()
{int arr[] = { 1,2,3,4,5 };vector<int> v(arr, arr + 5);for (auto e : v){cout << e << " ";  //1 2 3 4 5}
}

创建一个包含数组中所有元素的整数类型的vector


4.拷贝构造函数
vector (const vector& x);

  • 解释:构造一个容器,其中包含 x 中每个元素的副本,顺序相同
  • 示例:
int main()
{vector<int> v1(5, 2);  //创建一个包含5个元素,每个元素都是2的整形类型的vectorvector<int> v2(v1);  //创建一个与v1相同的vector——v2for (auto e : v2){cout << e << " ";  //2 2 2 2 2}
}

vector的使用

1.迭代器

begin()和end()
请添加图片描述

请添加图片描述

  • 解释:获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator
  • 示例:
void test_vector2()
{// const对象使用const迭代器进行遍历打印vector<int> v(5,10);vector<int>::const_iterator it = v.begin();//auto it = v.begin();while (it != v.end()){cout << *it << " ";  //10 10 10 10 10++it;}cout << endl;
}

rbegin()和rend()
请添加图片描述

请添加图片描述

  • 解释:获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的reverse_iterator
  • 示例:
void test_vector2()
{// 使用列表方式初始化,C++11新语法vector<int> v{ 1,2,3,4,5 };vector<int>::reverse_iterator rit = v.rbegin();//auto rit = v.rbegin();while (rit != v.rend()){cout << *rit << " ";  //5 4 3 2 1rit++;}
}

迭代器的示例:
请添加图片描述


2.vector的增删改查操作

push_back
请添加图片描述

  • 解释:push_back用于在vector的尾部插入一个新的元素。跟string的用法一样其中,value_type表示vector中存储的元素类型,val是要插入的新元素。
  • 示例:
void test_vector3()
{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 << " ";  //1 2 3 4++it;}
}

pop_back
请添加图片描述

  • 解释:删除vector的最后一个元素。相当于尾删。注意:如果容器为空,使用pop_back会产生未定义的行为。所以在用之前,要判断vector是否为空。
  • 示例:(接上面的代码)
void test_vector3()
{v.pop_back();v.pop_back();it = v.begin();cout << "尾删后的元素是:";while (it != v.end()){cout << * it << " ";  //尾删后的元素是:1 2++it;}
}

find
首先我们来看一下STL中的find操作。注意:vector没有提供find方法,如果要查找只能使用STL提供的全局find
请添加图片描述

  • 解释:在[first,last)这个范围内查找val,如果找到,会返回一个迭代器。
  • 示例:
void test_vector4()
{vector<int> v{ 1,3,5,7,9,2,4,6,8 };auto p = find(v.begin(), v.end(), 8);  //p的类型是int*的指针cout << *p << endl;  //8
}

insert
请添加图片描述

iterator insert (iterator position, const value_type& val);

  • 解释:在positions位置之前插入val
    void insert (iterator position, size_type n, const value_type& val);
  • 解释:在position位置之前插入n个val
    void insert (iterator position, InputIterator first, InputIterator last);
  • 解释:在position位置之前插入[first,last)这个范围内的数据
  • 示例:
void test_vector5()
{vector<int> v1(3, 20);  //创建一个包含3个元素,每个元素都是20的vectorvector<int>::iterator it;it = v1.begin();it = v1.insert(it, 40);  //在it之前插入40for (auto e : v1){cout << e << " ";  //40 20 20 20}cout << endl;v1.insert(it, 3, 5);  //在it之前插入3个元素都是5的数据for (auto e : v1){cout << e << " ";  //5 5 5 40 20 20 20}cout << endl;vector<int> v2(3, 1);  //创建一个包含3个元素,每个元素都是1的vectorint array[] = { 11, 12, 13, 14, 15 };v2.insert(v2.begin(), array, array + 3);  //在v2.begin()之前插入array数组中前3个元素for (auto e : v2){cout << e << " ";  //11 12 13 1 1 1}cout << endl;}

erase
请添加图片描述

iterator erase (iterator position);

  • 解释:在position这个位置进行删除数据。该函数返回一个指向被删除元素之后元素的迭代器。注意:position要使用迭代器形式
    iterator erase (iterator first, iterator last);
  • 解释:删除指定范围内的元素,如[first,last)区间内的元素。并返回一个指向最后一个被删除元素之后元素的迭代器。注意:first和last要使用迭代器形式
  • 示例:
void test_vector6()
{vector<int> v{ 5,4,3,2,1 };v.erase(v.begin() + 2);for (auto e : v){cout << e << " ";  //5 4 2 1}cout << endl;vector<int> v1{ 5,4,3,2,1 };v1.erase(v1.begin() + 1, v1.begin() + 3);for (auto e : v1){cout << e << " ";  //5 2 1//注意是左闭右开区间,所以删除的是下标为1和2位置的元素}
}

3.vector的访问操作

operator[]
请添加图片描述

  • 解释:它可以像数组一样进行访问。它的语法是vector_name[index],其中vector_namevector的名称,index是要访问的元素的索引。索引从0开始,表示vector中的第一个元素。
  • 示例:
void test_vector7()
{vector<int> v{ 5,4,3,2,1 };//访问操作cout << v[3] << endl;  //2//修改操作v[3] = 9;cout << v[3] << endl;  //9
}

empty
bool empty() const;

  • 解释:检查vector是否为空,为空返回1,不为空时返回0。
  • 示例:
void test_vector7()
{vector<int> v;int i = v.empty();cout << i << endl;if (v.empty())  //vector为空{cout << "The vector is empty" << endl;}else{cout << "The vector is not empty" << endl;}v.push_back(3);if (v.empty())  //vector不为空,{cout << "The vector is empty" << endl;}else{cout << "The vector is not empty" << endl;}
}

back和front
请添加图片描述
请添加图片描述

  • 解释:back返回的是容器中最后一个元素的引用,即最后一个元素的值。而front()vector容器的一个成员函数,返回的是容器中第一个元素的值。
  • 示例:
void test_vector8()
{vector<int> v{ 5,4,3,2,1 };cout << v.front() << endl;  //5cout << v.back() << endl;  //1auto it1 = v.begin();auto it2 = v.end();cout << *it1 << endl;  //5cout << *it2 << endl;  //程序崩溃。因为end是最后一个元素的下一个位置
}

4.vector的容量操作

size
size_type size() const;

  • 解释:获取数据个数
  • 示例:
void test_vector9()
{vector<int> v;cout << "0. size: " << v.size() << '\n';  //0. size: 0for (int i = 0; i < 10; i++){v.push_back(i);}cout << "1. size: " << v.size() << '\n';  //1. size: 10v.insert(v.end(), 10, 100);cout << "2. size: " << v.size() << '\n';  //2. size: 20v.pop_back();cout << "3. size: " << v.size() << '\n';  //3. size: 19
}

capacity
size_type capacity() const;

  • 解释:获取容量大小
  • 示例:
void test_vector10()
{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';}}
}

结果:(这是在vs2019下运行的,不同的编译器结果会有不同)

making v 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

vs下使用的STL基本是按照1.5倍方式扩容.


resize
void resize (size_type n, value_type val = value_type());

  • 解释:调整容器的大小,使其包含 n 个元素。简单来说是改变vector的size。
  • resize函数有2种情况:
    情况1
  • 如果 n 小于当前容器大小,则内容将减少到其前 n 个元素,删除超出(并销毁它们)的元素。
    情况2
  • 如果 n 大于当前容器大小,则通过在末尾插入所需数量的元素来扩展内容,以达到 n 的大小。如果指定了 val,则新元素将初始化为 val 的副本,否则,它们将进行值初始化。
  • 示例:
void test_vector11()
{vector<int> myvector;// set some initial content:for (int i = 1; i <= 10; i++){myvector.push_back(i);}myvector.resize(5);myvector.resize(8, 100);myvector.resize(12);cout << "myvector contains:";for (int i = 0; i < myvector.size(); i++){cout << ' ' << myvector[i];  //myvector contains: 1 2 3 4 5 100 100 100 0 0 0 0}
}

代码解释:先对myvector尾插了10个数据,然后使用resize将大小调整为5,所以只保留前5个元素;第2个resize是将大小调整为8,并且用100进行扩展,由于n从5变成8,所以有3个100;第3个resize是将n从8变成12,由于没有val,所以剩余的元素用0进行扩展。


reserve
void reserve (size_type n);

  • 解释:改变vector的capacity
  • 示例:
void test_vector12()
{vector<int>::size_type sz;vector<int> foo;sz = foo.capacity();cout << "making foo grow:\n";for (int i = 0; i < 100; ++i) {foo.push_back(i);if (sz != foo.capacity()) {sz = foo.capacity();cout << "capacity changed: " << sz << '\n';}}vector<int> bar;sz = bar.capacity();bar.reserve(100);   // this is the only difference with foo abovecout << "making bar grow:\n";for (int i = 0; i < 100; ++i) {bar.push_back(i);if (sz != bar.capacity()) {sz = bar.capacity();cout << "capacity changed: " << sz << '\n';}}
}

结果:(这是在vs2019下运行的,不同的编译器结果会有不同)

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
making bar grow:
capacity changed: 100


vector迭代器失效的问题(重点)

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

  1. 会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等。
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重新赋值即可。*/while (it != v.end()){cout << *it << " ";++it;}cout << endl;return 0;
}
  1. 指定位置元素的删除操作–erase
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就认为该位置迭代器失效了。
现在来看一个案例:删除vector中所有的偶数。

  • 先看错误写法
#include <iostream>
using namespace std;
#include <vector>int main()
{vector<int> v{ 1, 2, 3, 4 };auto it = v.begin();// vs2019进行强制检查,erase以后认为it失效了,不能访问,访问就报错while (it != v.end()){if (*it % 2 == 0)v.erase(it);++it;}return 0;
}

请添加图片描述

这是模拟实现vector中erase的写法,上面的图片中画出了出错的原因,同学们可以参考一下

  • 正确写法
int main()
{vector<int> v{ 1, 2, 3, 4 };auto it = v.begin();while (it != v.end()){if (*it % 2 == 0){it = v.erase(it);}else{++it;}}return 0;
}
  1. 与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;}
}

迭代器失效解决办法:在使用前,对迭代器重新赋值即可
注意:Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端.

vector深度剖析及模拟实现

std::vector的核心框架接口的模拟实现bit::vector

vector的模拟实现

使用memcpy拷贝问题

假设模拟实现的vector中的reserve接口中,使用memcpy进行的拷贝,以下代码会发生什么问题?

int main()
{bite::vector<bite::string> v;v.push_back("1111");v.push_back("2222");v.push_back("3333");return 0;
}

问题分析:

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

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

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

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

相关文章

Vue常用指令入门

1. v-for 作用&#xff1a;用于遍历对象或数组 注意&#xff1a;需要提供key属性&#xff0c;可以提高性能和避免渲染错误&#xff0c;值通常为index或item.id <li v-for"(item, index) in items" :key"index">{{ item }} </li>2. v-if,v-el…

在机器视觉检测中为何选择线阵工业相机?

线阵工业相机&#xff0c;顾名思义是成像传感器呈“线”状的。虽然也是二维图像&#xff0c;但极宽&#xff0c;几千个像素的宽度&#xff0c;而高度却只有几个像素的而已。一般在两种情况下使用这种相机&#xff1a; 1. 被测视野为细长的带状&#xff0c;多用于滚筒上检测的问…

线性DP:最长上升子序列(子序列可不连续,子数组必须连续)

目录 Q1&#xff1a;简单遍历 Q2&#xff1a;变式&#xff08;加大数据量&#xff09; Q1&#xff1a;简单遍历 Dp问题 状态表示 f(i,j) 集合所有以第i个数结尾的上升子序列集合-f(i,j)的值存的是什么序列长度最大值max- 状态计算 &#xff08;其实质是集合的划分&#xff09;…

【Web前端技术】第二节—HTML标签(上)

hello&#xff01;好久不见—— 做出一个属于自己的网站&#xff01; 云边有个稻草人-个人主页 Web前端技术—本篇文章所属专栏 目录 一、HTML 语法规范 1.1 基本语法概述 1.2 标签关系 二、HTML 基本结构标签 2.1 第一个 HTML 网页 2.2 基本结构标签总结 三、网页开发…

论文降重GPT指令-实侧有效从98%降低到8%

步骤1&#xff1a;文本接收 指令&#xff1a; 请用户提供需要优化的文本内容。 对文本进行初步分析&#xff0c;识别文本的基本结构和风格。 操作&#xff1a; 接收并分析用户提交的文本。 步骤2&#xff1a;文本优化 2.1 连接词处理 指令&#xff1a; 删除或替换连接词&#x…

Jsp技术入门指南【九】详细讲解JSTL

Jsp技术入门指南【九】详细讲解JSTL 前言一、什么是JSTL&#xff1f;&#xff08;JavaServer Pages Standard Tag Library&#xff09;二、使用JSTL前的准备三、核心标签库常用标签详解1. <c:out>&#xff1a;输出内容&#xff08;替代<% %>&#xff09;2. <c:i…

Linux操作系统--进程的创建和终止

目录 1.进程创建 1.1fork()函数初识 1.2写时拷贝 1. 提升系统效率 2. 隔离错误影响 3. 支持并行计算 2.进程终止&#xff1a; 2.1进程退出场景&#xff1a; 2.2进程常见退出方法&#xff1a; 2.3_exit()系统调用接口 2.4exit函数 2.5return退出 1.进程创建 1.1for…

OSPF综合实验——企业边界路由器、LSA收敛

IP划分粗略记号&#xff0c;方便后续配置 配置IP和环回--->ISP的IP配置和cheat认证---->配置OSPF和RIP---->企业边界路由网段汇总---->特殊区域---> 缺省路由&#xff0c;重分发---->nat配置---->实现全网通 路由器配置IP和环回地址 <Huawei>sys…

Java【网络原理】(4)HTTP协议

目录 1.前言 2.正文 2.1自定义协议 2.2HTTP协议 2.2.1抓包工具 2.2.2请求响应格式 2.2.2.1URL 2.2.2.2urlencode 2.2.3认识方法 2.2.3.1GET与POST 2.2.3.2PUT与DELETE 2.2.4请求头关键属性 3.小结 1.前言 哈喽大家好啊&#xff0c;今天来继续给大家带来Java中网络…

Android学习总结之APK打包流程

一、预处理阶段&#xff08;编译前准备&#xff09; 1. AIDL 文件处理&#xff08;进程间通信基础&#xff09; 流程&#xff1a; 用于实现 Android 系统中不同进程间的通信&#xff08;IPC&#xff09;。在项目构建时&#xff0c;AIDL 编译器会将 .aidl 文件编译为 Java 接口…

BDO分厂积极开展“五个一”安全活动

BDO分厂为规范化学习“五个一”活动主题&#xff0c;按照“上下联动、分头准备 、差异管理、资源共享”的原则&#xff0c;全面激活班组安全活动管理新模式&#xff0c;正在积极开展班组安全活动&#xff0c;以单元班组形式对每个班组每周组织一次“五个一”安全活动。 丁二醇单…

【音视频】FLV格式分析

FLV概述 FLV(Flash Video)是Adobe公司推出的⼀种流媒体格式&#xff0c;由于其封装后的⾳视频⽂件体积⼩、封装简单等特点&#xff0c;⾮常适合于互联⽹上使⽤。⽬前主流的视频⽹站基本都⽀持FLV。采⽤FLV格式封装的⽂件后缀为.flv。 FLV封装格式是由⼀个⽂件头(file header)和…

Java表达式1.0

Java开发工具 在当今的Java开发领域&#xff0c;IntelliJ IDEA已然成为了众多开发者心目中的首选利器&#xff0c;它被广泛认为是目前Java开发效率最快的IDE工具。这款备受瞩目的开发工具是由JetBrains公司精心打造的&#xff0c;而JetBrains公司总部位于风景如画的捷克共和国首…

Map遍历

第一种遍历方式键找值&#xff1a; 增强for循环&#xff1a; 通过获取元素中的键&#xff0c;get到对应的值&#xff0c;通过增强for循环获取集合里的键&#xff0c;然后用get方法通过键获取值 代码演示&#xff1a; import java.text.ParseException; import java.util.*;…

内网穿透服务器—FRP

某天某刻空闲的时候跟同事聊的本地的存储服务如果我想让其他公网内的用户使用&#xff08;这个存储服务只是一个临时文件传递站&#xff0c;碎文件&#xff0c;安全低的&#xff09;&#xff0c;然后我们就探讨到了FRP一个比较久远的技术&#xff0c;来做内网穿透&#xff0c;下…

力扣每日打卡16 781. 森林中的兔子(中等)

力扣 781. 森林中的兔子 中等 前言一、题目内容二、解题方法1. 哈希函数&#xff08;来自评论区大佬的解题方法&#xff09;2.官方题解2.1 方法一&#xff1a;贪心 前言 这是刷算法题的第十六天&#xff0c;用到的语言是JS 题目&#xff1a;力扣 781. 森林中的兔子 (中等) 一、…

基于深度学习的线性预测:创新应用与挑战

一、引言 1.1 研究背景 深度学习作为人工智能领域的重要分支&#xff0c;近年来在各个领域都取得了显著的进展。在线性预测领域&#xff0c;深度学习也逐渐兴起并展现出强大的潜力。传统的线性预测方法在处理复杂数据和动态变化的情况时往往存在一定的局限性。而深度学习凭借…

黑马点评redis改 part 3

优惠券秒杀 全局唯一id 每个店铺都可以发布优惠券&#xff1a; 当用户抢购时&#xff0c;就会生成订单并保存到tb_voucher_order这张表中&#xff0c;而订单表如果使用数据库自增ID就存在一些问题&#xff1a;实际开发中数据库ID一般不会参与业务逻辑 增加一个订单号字段就好…

低代码开发平台:企业数字化转型的加速器

一、引言 在数字化时代&#xff0c;企业的转型需求日益迫切。为了在激烈的市场竞争中保持领先地位&#xff0c;企业需要快速响应市场变化、优化业务流程、提升运营效率。然而&#xff0c;传统的软件开发模式往往面临开发周期长、成本高、灵活性差等问题&#xff0c;难以满足企业…

个人所得税

文章目录 一、名词解释二、个人所得税计算方法 (举例)1.累计预扣预缴应纳税所得额、本期应预扣预缴税额2.个人所得税预扣率表一3.个人所得税计算举例 三、专项附加扣除政策介绍四、年度汇算清缴政策介绍五、常见问答 一、名词解释 累计预扣法是指扣缴义务人在一个纳税年度内预…