C++初阶-vector类的模拟实现

vector类的模拟实现

  • 一、经典的vector类问题
    • 1.1 前期准备
  • 二、vector的默认成员函数
    • 2.1 构造函数
      • 2.1.1 无参构造
      • 2.1.2 构造具有n个对象值为val的容器(数据类型为模板类型T)
      • 2.1.3 拷贝构造
    • 2.2 swap(operator=需要用)
    • 2.3 复制重载operator=
    • 2.4 析构函数
  • 三、vector的三种遍历方式
    • 3.1 size和capacity
    • 3.2 operator[]遍历
    • 3.3 迭代器iterator遍历和范围for
  • 四、vector相关的增容和删除
    • 4.1 reserve
    • 4.2 resize
    • 4.3 push_back
    • 4.4 pop_back
    • 4.5 insert
    • 4.6 erase
    • 4.7 empty
  • 五、完整代码
    • 5.1 vector.h
    • 5.2 test.cpp

一、经典的vector类问题

C++ STL中的vector就类似于C语言当中的数组,但是vector又拥有很多数组没有的接口,使用起来更加方便。
相比于STL中的string,vector可以定义不同的数据类型。

1.1 前期准备

迭代器的本质可以暂时看作是指针,模拟实现vector,需要定义三个指针:指向起始位置_start,指向最后一个有效元素的下一个位置_finish,指向容器最后一个位置_end_of_storage
在这里插入图片描述

namespace zl {  //自定义命名空间template<class T>     //定义模板类型class vector{public:typedef T* iterator;   //迭代器  等价于  T 类型指针private:iterator _start;   //空间起始位置iterator _finish;  //最后一个有效元素的下一个位置iterator _end_of_storage;  //最后一个容量空间位置};
}

二、vector的默认成员函数

2.1 构造函数

2.1.1 无参构造

构造一个空vector,size和capacity为0,将_start,_finish,_end_of_storage都置为空指针即可

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

2.1.2 构造具有n个对象值为val的容器(数据类型为模板类型T)

vector(size_t n, const T& val = T())
{:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)reserve(n);for (size_t i = 0; i < n; ++i){push_back(val);}
}vector(int n, const T& val = T())
{:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)reserve(n);for (int i = 0; i < n; ++i){push_back(val);}
}

2.1.3 拷贝构造

传统写法
(1)新开辟一块和v同样容量的空间,更新_startt,_finish,_end_of_storage
(2)将v中的数据拷贝到新开辟的空间中

vector(const vector<T>& v)
{:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)//reserve(v.capacity());_start = new T[v.capacity()];//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();
}

现代写法
使用范围for进行遍历,变量e是v中元素的拷贝,如果v中元素是需要深拷贝的自定义类型,会调用拷贝构造函数构造e,从而使e和v中元素所指向的空间不一样(auto& e:v也可以,因为push_back在实现的时候还会调用深拷贝类型的赋值运算符重载)

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

新写法

vector(const vector<T>& v)
{vector<T> tmp(v.begin(), v.end());  //定义临时对象--调用迭代器构造方法swap(tmp);   //进行资源交换
}

2.2 swap(operator=需要用)

调用全局的swap,交换指针,其为内置类型,无需深拷贝,代价相比于直接调用库里面函数交换比较小

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.3 复制重载operator=

传统写法
(1)释放原空间,新开一块容量和v一样大的空间,更新_start,_finish,_end_of_storage
(2)将v中的数据拷贝到新空间中

vector<T>& operator=(const vector<T>& v)
{if (this != &v){delete[]_start; // 释放原空间_start = new T[v.capacity()]; // 开辟新空间for (size_t i = 0; i < v.size(); i++) // 拷贝数据{_start[i] = v[i];}_finish = _start + v.size(); // 更新_finish_endofstorage = _start + v.capacity(); // 更新_capacity}return *this;
}

现代写法
(1)调用拷贝构造函数生成tmp对象
(2)分别交换tmp和this的_start,_finish,_end_of_storage

vector<T>& operator=(const vector<T>& v)
{if (this != &v) // 防止自己给自己赋值{vector<T> tmp(v); // 拷贝构造tmp对象swap(tmp); // 交换_start,_finish, _endofstorage}return *this;
}
vector<T>& operator=(vector<T> v) // 拷贝构造v对象
{swap(v); // 交换_start,_finish, _endofstoragereturn *this;
}

2.4 析构函数

将空间释放掉,_start,_finish,_end_of_storage置为空指针

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

三、vector的三种遍历方式

3.1 size和capacity

size返回容器中有效数据的个数,用_finish-_start即可

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

capacity返回容器的容量,用_end_of_storage-_start即可

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

3.2 operator[]遍历

返回底层数组对应位置的引用即可,需要用const和非const两个版本

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

3.3 迭代器iterator遍历和范围for

迭代器也需要写const和非const两个版本,且一个容器只要支持迭代器,就可以用范围for操作,因为范围for本质也是迭代器

iterator begin()
{return _start;
}iterator end()
{return _finish;
}const_iterator begin() const
{return _start;
}const_iterator end() const
{return _finish;
}

四、vector相关的增容和删除

4.1 reserve

(1)当n大于对象当前的capacity时,将capacity扩大到n或大于n
(2)当n小于对象当前的capacity时,什么也不做
增容前就需要算出size的大小
因为增容涉及新和旧空间的问题,比如_finish=tmp+size(),而size的求法是用_finish-_start,但_start已指向新空间,可_finish还是指向旧空间,两块不连续的空间相减就是随机值了,故要在增容之前就把大小算出来,不然增容之后的大小就是随机值了。
reserve需要更新_finish
因为_finish是指针,如果一个对象它上来就reserve保存一定容量,然后直接扩容,但是插入数据肯定涉及*_finish操作,若不更新_finish,_finish初始化为nullptr就非法访问内存了,故reserve中也要更改_finish。
不能使用memcpy拷贝数据
因为增容中使用了memcpy,memcpy导致了问题的出现,因为memcpy是按字节拷贝的,它本质上造成了浅拷贝问题,memcpy使新旧空间都指向了一份数据,旧空间释放后,它指向的对应数据也被释放,而新空间指针还是指向这份旧空间,这就造成了非法访问,所以新空间与旧空间不应该指向同一份数据,应该不用memcopy,写成深拷贝(将旧空间的每个值赋值给新空间对应的每个值就好了,就很实现深拷贝了,指向不同的数据)
在这里插入图片描述

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 = tmp + sz;_end_of_storage = tmp + n;}
}

4.2 resize

(1)当n<size时,直接将_finish=_start+n(将有效数据长度缩小)即可
(2)当size<n<=capacity时,将有效数据的长度增加到n,增加出来的有效数据内容是val
(3)当n>capacity时,先调用上面的reserve函数进行增容,再将有效数据的长度增加到n,增加出来的有效数据内容是val

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

4.3 push_back

尾插数据,首先要检查是否已满,已满则进行增容,增容后尾插即可

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

4.4 pop_back

尾插时,首先要判断容器是否为空,若为空,则断言报错,不为空,_finish–即可

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

4.5 insert

(1)容量不够,先增容,增容前先记录pos-_start的值,否则增容之后,pos还指向原来已经被释放的空间
(2)将pos位置往后的数据往后挪一位,在pos位置插入值val
为什么需要提前计算出len
有迭代器失效的问题,因为pos是迭代器类型,扩容前指向旧空间的某一位置,而reserve调用后会扩容,而我们是扩容完才插入数据的,此时pos无效,因为旧空间已经释放了,它这个迭代器还指向那里就失效了,故我们要更新pos位置,使它指向新空间,所以要先算len,即原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,解决pos失效的问题pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = val;++_finish;return pos;
}

4.6 erase

erase是返回被删除数据的下一个位置,当要被删除的数据被删除了,erase原地不动的话,就已自动指向了下一位置,因为那个数据被删除了

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

4.7 empty

判断是否为空

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

五、完整代码

5.1 vector.h

#pragma once
#include <assert.h>namespace zl
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;vector()/*:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)*/{}vector(size_t n, const T& val = T()){/*:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)*/reserve(n);for (size_t i = 0; i < n; ++i){push_back(val);}}vector(int n, const T& val = T()){/*:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)*/reserve(n);for (int i = 0; i < n; ++i){push_back(val);}}/*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(_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<T>& v){vector<T> tmp(v.begin(), v.end());swap(tmp);}//void swap(vector& v)void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);}//v1=v2//vector& operator=(vector v)vector<T>& operator=(vector<T> v){swap(v);return *this;}//[first,last)template<class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}~vector(){delete[] _start;_start = _finish = _end_of_storage = nullptr;}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;}}}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 = tmp + sz;_end_of_storage = tmp + 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,解决pos失效的问题pos = _start + len;}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;}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= nullptr;iterator _finish= nullptr;iterator _end_of_storage= nullptr;};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;}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);func(v1);for (size_t i = 0; i < v1.size(); ++i){cout << v1[i] << " ";}cout << endl;v1.pop_back();v1.pop_back();v1.pop_back();//v1.pop_back();//v1.pop_back();//v1.pop_back();vector<int>::iterator it = v1.begin();while (it != v1.end()){cout << *it << " ";++it;}cout << endl;for (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* ();//	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);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 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);func(v1);v1.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)++;func(v1);}void test_vector4(){vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);//v1.push_back(5);func(v1);auto pos = find(v1.begin(), v1.end(), 3);if (pos != v1.end()){v1.erase(pos);}//erase以后,pos是否失效//pos失效了,不能访问并且进行强制检查//失效,不要访问,行为结果未定义   根据具体编译器实现有关,在g++中没报错 但是在vs下进行报错//(*pos)++;func(v1);}void test_vector5(){vector<int> v1;v1.push_back(10);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);func(v1);//要求删除所有的偶数vector<int>::iterator it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){it=v1.erase(it);}else{++it;}}func(v1);}void test_vector6(){//vector<int> v1(10u,5);    //u表示这个字面量为无符号vector<int> v1(10,5);   func(v1);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[] = {30,20,10};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;for (auto e : a){cout << e << " ";}cout << endl;//sort(a, a + sizeof(a) / sizeof(int));   //默认是升序<     降序>//greater<int> g;//sort(a, a + sizeof(a) / sizeof(int), g);sort(a, a + sizeof(a) / sizeof(int), greater<int> ());sort(a, a + sizeof(a) / sizeof(int));   for (auto e : a){cout << e << " ";}cout << endl;}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, "1111111111111111");for (auto e : v3){cout << e << " ";}cout << endl;vector<std::string> v4(v3);for (auto e : v4){cout << e << " ";}cout << endl;}}

5.2 test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<functional>
#include<string>
#include<vector>
using namespace std;
#include "vector.h"class A
{
public:A(){cout << "A()" << endl;}~A(){cout << "A()" << endl;}};int main()
{/*vector<int>::iterator it;cout << typeid(it).name() << endl;*///zl::test_vector1();//zl::test_vector2();//zl::test_vector3();//zl::test_vector4();//zl::test_vector5();//zl::test_vector6();zl::test_vector7();//A a1;A();     //匿名对象生命周期只在当前一行,因为这行之后没人会用它了//const A& xx = A();    //const引用会延长匿名对象的生命周期到引用对象域结束,因为以后用xx就是用匿名对象//A a2;return 0;
}

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

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

相关文章

volatile 系列之指令重排序导致的可见性问题

什么是指令重排序呢?为了更加直观地理解&#xff0c;老司机还是通过一个案例来说明 public class MemoryReorderingExample {private static int x0,y0:private static int a0,b0;public static void main(String[] args) throws InterruptedException {int i0;while(true){…

排序算法之一:直接插入排序

1.基本思想 直接插入排序是一种简单的插入排序法&#xff0c;其基本思想是&#xff1a; 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中&#xff0c;直到所有的记录插入完为止&#xff0c;得到一个新的有序序列 实际中我们玩扑克牌时&#xff0c;就用…

【UML】组件图中的供接口和需接口与面向对象中的接口

UML&#xff08;统一建模语言&#xff09;组件图中的“供接口”&#xff08;Provided Interface&#xff09;和“需接口”&#xff08;Required Interface&#xff09;与面向对象编程中的接口概念有关联&#xff0c;但它们在应用上有所区别。 下面解释两者的关系&#xff1a; …

NCNN 源码学习【一】:学习顺序

最近一段时间一直在做模型部署的工作&#xff0c;主要是利用NCNN部署到安卓端&#xff0c;跟着网上的博客和开源项目&#xff0c;做了很多工作&#xff0c;也学习到很多东西&#xff0c;但是对于NCNN的源码&#xff0c;并没有仔细的研究过&#xff0c;对我来说&#xff0c;仿佛…

C++共享和保护——(2)生存期

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 生命如同寓言&#xff0c;其价值不在于…

改进YOLOv8注意力系列二:结合CBAM、Coordinate Attention、deformable_LKA_Attention可变形大核注意力

改进YOLOv8注意力系列二:结合ACmix、Biformer、BAM注意力机制 代码CBAM注意力Coordinate Attention坐标注意力deformable_LKA_Attention可变形大核注意力加入方法各种yaml加入结构本文提供了改进 YOLOv8注意力系列包含不同的注意力机制以及多种加入方式,在本文中具有完整的代…

C语言之文件操作(上)

C语言之文件操作&#xff08;上&#xff09; 文章目录 C语言之文件操作&#xff08;上&#xff09;1. 什么是⽂件&#xff1f;1.1 程序⽂件1.2 数据⽂件1.3 ⽂件名 2. ⼆进制⽂件和⽂本⽂件3. ⽂件的打开和关闭3.1 流和标准流3.1.1 流3.1.2 标准流 4. ⽂件指针5. 文件的打开与关…

【广州华锐视点】物流数字孪生三维可视化系统打造更高效、智能的物流管理体验

在当今快速发展的物流行业中&#xff0c;传统的管理和监控方法往往难以满足复杂运营的需求。为了解决这个问题&#xff0c;广州华锐互动提供物流数字孪生三维可视化系统定制开发服务&#xff0c;打造更为高效、智能的物流管理体验。 物流数字孪生三维可视化系统是一种基于虚拟现…

虚拟现实三维电子沙盘数字沙盘开发教程第5课

虚拟现实三维电子沙盘数字沙盘无人机倾斜摄影全景建模开发教程第5课 设置system.ini 如下内容 Server122.112.229.220 userGisTest Passwordchinamtouch.com 该数据库中只提供 成都市火车南站附近的数据请注意&#xff0c;104.0648,30.61658 在鼠标指定的位置增加自己的UI对象&…

【Hive】——DDL(CREATE TABLE)

1 CREATE TABLE 建表语法 2 Hive 数据类型 2.1 原生数据类型 2.2 复杂数据类型 2.3 Hive 隐式转换 2.4 Hive 显式转换 2.5 注意 3 SerDe机制 3.1 读写文件机制 3.2 SerDe相关语法 3.2.1 指定序列化类&#xff08;ROW FORMAT SERDE ‘’&#xff09; 3.2.2 指定分隔符&#xff0…

深入理解Dubbo-8.Dubbo的失败重试设计

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理、分布式技术原理&#x1f525;如果感觉博主的文章还不错的话&#xff…

鸿蒙(HarmonyOS)北向开发项目编译问题汇总

运行Hello World Hello World 工程可以运行在模拟器中&#xff0c;或者运行在真机设备中。本示例先以选择将 Hello World 工程运行在模拟器中进行说明&#xff0c;如果选择运行在真机设备中&#xff0c;需要先对工程进行签名&#xff0c;然后才能运行在真机设备中。 DevEco S…

从零到一:influxdb时序性数据库的基本概念与操作指南

目录 ​编辑 引言 数据库(database) 创建数据库 删除数据库 进入数据库 展示influxdb中所有数据库 测量&#xff08;measurement&#xff09; 写入测量 展示测量 总结 引言 InfluxDB是一个开源的时序数据库&#xff0c;专门设计用于处理时间序列数据。它是由InfluxD…

C# 两个日期比较大小

文章目录 C# 两个日期比较大小直接比较大小工具类DateTime.Compare C# 两个日期比较大小 直接比较大小 string ed "2023-12-13 09:27:59.000";//过去式DateTime nowDateTime DateTime.Now;DateTime expirationDate Convert.ToDateTime(ed);//质保期 长日期DateT…

一行奇异代码,解决transition过渡动画无效问题!

一行奇异代码&#xff0c;解决transition过渡动画无效问题&#xff01; 无效的transition过渡动画 你是否遇到过这种情况&#xff1a;在css中设置了transition过渡动画&#xff0c;但使用时&#xff0c;确无效。 例如以下代码&#xff0c;便是一例&#xff1a; 在此代码中&a…

【云原生kubernets】Service 的功能与应用

一、Service介绍 在kubernetes中&#xff0c;pod是应用程序的载体&#xff0c;我们可以通过pod的ip来访问应用程序&#xff0c;但是pod的ip地址不是固定的&#xff0c;这也就意味着不方便直接采用pod的ip对服务进行访问。为了解决这个问题&#xff0c;kubernetes提供了Service资…

慎用,Mybatis-Plus这个方法可能导致死锁

1 场景还原 1.1 版本信息 MySQL版本&#xff1a;5.6.36-82.1-log Mybatis-Plus的starter版本&#xff1a;3.3.2 存储引擎&#xff1a;InnoDB1.2 死锁现象 A同学在生产环境使用了Mybatis-Plus提供的 com.baomidou.mybatisplus.extension.service.IService#saveOrUpdate(T, co…

Linux中使用podman管理容器

本章主要介绍使用podman管理容器 了解什么是容器&#xff0c;容器和镜像的关系安装和配置podman拉取和删除镜像给镜像打标签导出和导入镜像创建和删除镜像数据卷的使用管理容器的命令使用普通用户管理容器 对于初学者来说&#xff0c;不太容易理解什么是容器&#xff0c;这里…

挑战与创新:光学字符识别技术在处理复杂表格结构中的应用

OCR&#xff08;Optical Character Recognition&#xff09;光学字符识别技术是指通过计算机软硬件将印刷或手写的字符转化为可编辑和搜索的文本。这项技术已经被广泛应用于各个领域&#xff0c;例如扫描文档、自动化数据输入、图书数字化等。但是&#xff0c;当涉及到处理复杂…

“ABCD“[(int)qrand() % 4]作用

ABCD[(int)qrand() % 4] 作用 具体来说&#xff1a; qrand() 是一个函数&#xff0c;通常在C中用于生成一个随机整数。% 4 会取 qrand() 生成的随机数除以4的余数。因为4只有四个不同的余数&#xff08;0, 1, 2, 3&#xff09;&#xff0c;所以这实际上会生成一个0到3之间的随…