目录

 1. insert的实现

2. 迭代器失效

2.1 迭代器失效的两种情况

指向已释放的内存(物理失效)

元素移动导致迭代器指向错误(逻辑失效)

2.2 修改代码

3. erase的实现

​编辑修改代码

 4. resize的实现

5. 构造函数

5.1 默认构造函数

5.2 使用迭代器范围构造

5.3 使用n个val构造

 6. 拷贝构造函数

7. 赋值运算符重载

8. 关于vector对象的浅拷贝问题


 1. insert的实现

reserve(n)函数的核心逻辑是:当容器当前容量不足以容纳新元素时,会重新申请一块更大的内存空间,将原来元素复制到新空间中,然后释放旧的内存空间。

在下面这段代码中进行插入操作时,扩容后pos仍然指向旧的内存位置,而旧的内存已经被释放,循环条件end>=pos地址比较毫无意义,对*pos赋值时会导致程序崩溃。

//错误示例
void insert(iterator pos, const T& x)
{assert(pos >= _start);assert(pos <= _finish);//扩容  若触发扩容,旧内存被释放,pos失效if (_finish ==_end_of_storage){		reserve(capacity() == 0 ? 4 : capacity() * 2);	}iterator end = _finish - 1;//向后挪动数据while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;
}

正确做法:先记录pos相对于起始位置的偏移量,扩容后再根据偏移量来更新pos。

//right!
void insert(iterator pos, const T& x)
{	assert(pos >= _start);assert(pos <= _finish);	//扩容	if (_finish ==_end_of_storage){//记录pos相对于起始地址的偏移量size_t len = pos - _start;//扩容(重新申请内存,旧内存释放)reserve(capacity() == 0 ? 4 : capacity() * 2);//扩容后,更新pos到新内存的对应位置pos = _start + len;}iterator end = _finish - 1;//挪动数据while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;
}

2. 迭代器失效

2.1 迭代器失效的两种情况

指向已释放的内存(物理失效)

//插入元素
void insert(iterator pos, const T& x)
{assert(pos >= _start);assert(pos <= _finish);//扩容if (_finish == _end_of_storage){//记录pos相对于起始地址的偏移量size_t len = pos - _start;//扩容(重新申请内存,旧内存释放)reserve(capacity() == 0 ? 4 : capacity() * 2);//扩容后,更新pos到新内存的对应位置pos = _start + len;}iterator end = _finish - 1;//挪动数据while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;
}
……
//测试调用void test_vector2(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);//再插入元素则需要扩容print_vector(v);int x;cin >> x;auto p = find(v.begin(), v.end(), x);if (p != v.end()){v.insert(p, 300); //传入p的拷贝给insert的形参pos  形参的改变不影响实参 //(*p) *= 10;     //错误!进行扩容时insert内部更新迭代器pos,但不影响实参p,p仍指向旧位置,所以外部p已失效,访问可能会出错。}				print_vector(v);}

元素移动导致迭代器指向错误(逻辑失效)

自定义insert函数中,即使不扩容,迭代器也会失效。

原因:插入时,pos及之后的元素会向后挪动一位,假设迭代器p指向位置 i ,插入后原位置i的元素被移动到 i+1,新元素被插入到 i 位置,p仍指向物理地址 i ,但该地址的元素已经不是原来的了,p指向的“语义”已经改变,属于逻辑失效

在VS环境下,标准库vector的迭代器失效检查非常严格,即使不发生扩容,insert后使用原迭代器也会触发调试错误。

总结:迭代器的 “失效” 不仅指 “指向已释放内存”(物理失效),还包括 “容器结构改变导致指向逻辑错误”(逻辑失效)。这也是为什么标准库文档强调:对容器执行修改操作(insert/erase 等)后,所有可能受影响的迭代器都应视为失效,不应再使用。

2.2 修改代码

插入操作(无论是否扩容)都会导致原迭代器可能失效:

  • 若发生扩容:旧内存被释放,原迭代器指向无效地址(物理失效)。
  • 若未扩容:元素后移导致原迭代器指向的 “语义位置” 改变(逻辑失效,比如原迭代器本应指向元素 A,插入后指向的是新元素)。

让 insert 返回更新后的 pos 迭代器,这也是STL标准库中 insert 的原型,STL中vector::insert 的原型正是:iterator  insert(iteartor  pos ,const  T&  value);

iterator insert(iterator pos, const T& x)
{assert(pos >= _start);assert(pos <= _finish);//扩容if (_finish == _end_of_storage){//记录pos相对于起始地址的偏移量size_t len = pos - _start;//扩容(重新申请内存,旧内存释放)reserve(capacity() == 0 ? 4 : capacity() * 2);//扩容后,更新pos到新内存的对应位置pos = _start + len;}iterator end = _finish - 1;//挪动数据while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;  //返回更新后的pos迭代器! ! ! !
}……
//安全使用	
void test_vector2(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);print_vector(v);int x;cin >> x;auto p = find(v.begin(), v.end(), x);if (p != v.end()){p = v.insert(p, 99);//用insert的返回值更新p,获取新的有效迭代器p现在指向新插入的99(*p) *= 10;         //安全操作:此时p有效,修改后新元素变为990}			print_vector(v);}

3. erase的实现

//错误示例
//删除偶数元素
void erase(iterator pos)
{assert(pos >= _start);assert(pos < _finish);iterator it = pos + 1;while (it != end()){*(it-1) = *it;++it;}--_finish;			
}//测试调用  删除所有的偶数
auto it = v.begin();
while (it != v.end())
{if (*it % 2 == 0){v.erase(it);  //删除后it已失效}++it;	//错误:无论是否删除都对it自增
}print_vector(v);

对于上面erase的实现大有问题:

  • 如果v=[1,2,4,5]含有连续的偶数,按当前逻辑执行,最终会导致v=[1,4,5],偶数4被漏删,逻辑错误
  • 如果v=[1,2,3,4],那么当it超出有效范围[_start,_finish]后,*it属于未定义行为,程序可能读取到任意值,也可能直接崩溃,如果碰巧读取到一个偶数,那么就会发生断言错误

修改代码

对于上面的逻辑错误,我们只需要添加else分支,将++it封装到else里面即可解决问题。

//删除元素
void erase(iterator pos)
{assert(pos >= _start);assert(pos < _finish);iterator it = pos + 1;while (it != end()){*(it-1) = *it;++it;}--_finish;
}//……//删除所有的偶数
auto it = v.begin();
while (it != v.end())
{if (*it % 2 == 0){v.erase(it);				}else{++it;}			
}

这种写法在数组式容器中能正常工作,是因为当删除it指向的元素后,it并没有失效(仍指向原来的内存地址),但该地址的值已经变成了“原下一个元素”。此时不执行++it,下一轮循环会直接检查这个值,相当于间接实现了“迭代器后移”的效果。

但这种写法有两个隐藏的风险

1.但是在erase函数里面有扩容逻辑时 ,it指向已释放的内存,可能导致程序崩溃。

2. 依赖容器底层实现,只有当容器是连续内存存储时,逻辑才成立。如果换成链表式容器,链表的底层是离散的节点,每个节点存“数据”和“指向下一节点的指针”,节点间通过指针连接,内存不连续。删除节点后 it 会指向“已释放的内存”,下一次循环解引用*it时可能直接崩溃,或读取到垃圾数据。

如果想让代码更健壮、更符合常规设计,建议让erase函数返回“下一个有效迭代器”,并使用标准写法:it=v.erase(it),这样无论扩不扩容,无论容器底层是数组还是链表,逻辑都能保持一致且安全。

正确代码:

//删除元素
iterator erase(iterator pos)
{assert(pos >= _start);assert(pos < _finish);iterator it = pos + 1;while (it != end()){*(it-1) = *it;++it;}--_finish;return pos;  //返回下一个有效迭代器
}//……//删除所有的偶数
auto it = v.begin();
while (it != v.end())
{if (*it % 2 == 0){it = v.erase(it); //用返回值更新 it(指向删除后的下一个元素)}else{++it;}			
}

这一设计符合 STL 规范,能确保迭代器操作的安全性,逻辑与 STL 标准库vector::erase完全一致。

 4. resize的实现

resize的作用是调整容器的大小,当容器需要扩展大小时,val来初始化所有新增的元素;如果不传递这个参数,就使用元素类型T的默认构造值(比如内置类型是 0,自定义类型调用默认构造)。

函数原型:void resize( size_t n, const T& val= T() ) ,这样写的意思就是调用T类型的默认构造函数生成匿名对象(临时对象),引用绑定到临时对象上,T是int时,T()就是0;T是自定义类时,调用其默认构造函数创建对象。使用常量引用,传递参数时可以避免拷贝,同时保证了val不会被修改。

也可以这样写:void resize( size_t n, T val = T() ) ,这里 = T()表示调用T类型的默认构造函数生成的临时对象,再调用拷贝构造函数构造val,编译器可能会优化为直接构造val。

void resize(size_t n, const T& val= T()) //第二个参数也可以写作  T val = T()
{if (n < size()){_finish = _start + n;}else{reserve(n);while (_finish < _start + n){*_finish = val;++_finish;}}
}

5. 构造函数

5.1 默认构造函数

//构造函数vector()   //自定义空构造:_start / _finish / _end_of_storage 默认初始化(垃圾值)
{}//或 vector() = default;  C++11 引入,编译器生成的默认构造函数同样让三个指针默认初始化(垃圾值)

对于第一种写法,用户自定义的空构造函数,编译器不再生成默认构造。函数体为空,且没有成员初始化列表,此时成员变量会执行默认初始化

默认初始化规则:

  • 若成员是内置类型(如int、指针):默认初始化是“不初始化”,值为随机垃圾值。
  • 若成员是类类型(如string、自定义类):默认初始化是“调用该类的默认构造函数”。

对于第二种写法,是c++11及以后的标准中合法且推荐的写法,用于显式要求编译器生成“默认的构造函数”,编译器生成的默认构造函数同样会对成员变量进行默认初始化,两种写法效果类似,但=default  表意更清晰,是更推荐的写法。

5.2 使用迭代器范围构造

template <class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last)//使用!= 是考虑容器是链表的情况{push_back(*first);++first;}
}

它的作用是通过一段迭代器范围[first,last)来初始化vector对象,这个构造函数的特点在于,它本身也是一个函数模版。为什么需要把它设计成为一个函数模版呢?

当使用一段迭代器范围来初始化vector时,这段范围可能来自一个数组、链表,为了兼容这些不同类型的迭代器,这个构造函数必须自身成为一个函数模版。如果固定使用类内部的iterator,就无法使用其它容器初始化vector。

如果构造函数写成固定使用类内部的iterator会导致下面问题:

template <classT>class vector 
{
public:
typedef T* iterator;
typedef const T* const_iterator; // 仅接受自身迭代器的构造函数(局限性很大)vector(iterator first, iterator last) {while (first != last) {push_back(*first);++first;}}// ...
};
//无法用数组初始化vector
int arr[] = {1,2,3,4};
vector<int> v(arr, arr+4);  // 编译失败!arr是int*,不是vector<int>::iterator//也无法用list的迭代器初始化vector
std::list<int> lt = {1,2,3};
vector<int> v(lt.begin(), lt.end());  // 编译失败!list的迭代器不是vector的iterator

它的实际用法:

// 1. 从数组初始化vector
int arr[] = {1, 2, 3, 4, 5};
vector<int> v1(arr, arr + 5);// 2. 从另一个容器(list)的迭代器区间初始化vector
std::list<double> lst = {1.1, 2.2, 3.3};
vector<double> v2(lst.begin(), lst.end());  // 3. 从同类型vector的迭代器区间初始化vector
vector<string> v3 = {"a", "b", "c"};
vector<string> v4(v3.begin() + 1, v3.end()); 

这个函数是vector类模版的模版构造函数,属于带参的构造函数的一种,这样设计实现了“从任意序列范围初始化vector的功能”,极大提升了容器的通用性,这也是C++标准库std::vector的标准构造函数之一。

5.3 使用n个val构造

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

但是,这样写会出现下面的情况:

//模版构造函数
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last)//使用!= 是考虑容器是链表的情况{push_back(*first);++first;}
}vector(size_t n,const T& val=T())
{reserve(n);for (size_t i = 0; i < n; i++){push_back(val);}
}//……vector<string> v6(10, "11111111");
print_container(v6);vector<int> v7(10);
print_container(v7);vector<int> v8(10, 1); //这里报错!
print_container(v8);

为什么v6、v7都能正常运行,而v8却出错了呢?

C++函数重载决议规定,当有多个构造函数可以匹配时,编译器会选择最匹配的那个。问题出在类型匹配上,v7的初始化只提供了一个参数10,不会匹配到模版构造函数,核心原因是参数数量不匹配,此构造函数的第一个参数是size_t类型,需要将int转换为size_t类型。

v8的初始化提供了两个参数,(10是int,1是int)

对于模版构造函数,可以直接推导出 InputIterator = int,,此时两个参数类型完全匹配,不需要任何转换。对于第二个构造函数,第一个参数需要走隐式类型转换将int类型的10转换为siez_t类型。

所以编译器认为模板构造函数是更匹配的,从而选择了它。但是在模版构造函数中使用push_back(*first); first是int类型,解引用int是不合法的,所以会报错。

解决方法:让第二个构造函数的参数匹配度更高,将size_t 改成 int,构造函数对(int , int)类型的调用实现 “完全匹配”。

当非模板函数和模板函数都能提供 “同样好的匹配” 时,非模板函数的优先级更高,此时v8会调用第二个构造函数。

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

 6. 拷贝构造函数

//拷贝构造函数
vector(const vector<T>& v)
{reserve(v.size());for (auto& e : v){push_back(e);}
}

当通过拷贝构造函数创建新对象时,新对象的三个指针作为内置类型会先执行默认初始化,值为随机值,后续通过 reserve(v.size()); 一次性分配足够内存,并将这三个指针显式赋值,从而完成它们的 “有效初始化”,通过后续操作(push_back)来规范它们的值。

7. 赋值运算符重载

传统写法:

//赋值  v1 = v3
vector<T>& operator=(const vector<T>& v)
{if (this != &v) //避免自己给自己赋值{clear();//清理数据,但保留空间reserve(v.size());//一次扩容到位,避免频繁扩容for (auto& e : v){push_back(e);}}return *this;
}

现代写法:

//交换函数
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 = v3  
vector<T>& operator=(vector<T> v) //这里是值传递 也可以这样写:vector& operator=(vector v)
{swap(v);return *this;
}

当函数参数是值传递时,会触发拷贝构造函数,将v3拷贝一份到参数v中(v是v3的副本),swap(v)交换的是v1和v3的副本,交换后当前对象*this有了v3的资源,完成了赋值,参数v拥有了当前对象原来的旧资源,函数结束时,v会被销毁(触发析构函数),避免了内存泄漏。这种写法天然处理了自赋值,不会出现资源被提前释放的问题。

在这个模板类的内部,编译器已经知道当前处于的vector<T>的作用域中,所以在模板类的内部成员函数定义中,可以直接用类名vector 代替完整的vector<T>,这是 C++ 的语法规则允许的简写,核心原因是模板类的作用域内会自动关联模板参数。

8. 关于vector<string>对象的浅拷贝问题

vector<string> v;
v.push_back("1111111111111111111");
v.push_back("1111111111111111111");
v.push_back("1111111111111111111");
v.push_back("1111111111111111111");
print_vector(v);v.push_back("1111111111111111111"); //扩容
print_vector(v);

运行结果:

为什么会出现乱码呢?

核心原因是:memcpy是浅拷贝,而std::string是带动态内存管理的类,浅拷贝会导致多个string对象共享同一块堆内存,析构时重复释放或非法访问已释放内存。

std::string的内部结构(简化理解):

class string {
private:char* _data; // 指向堆上的字符缓冲区size_t _size;size_t _capacity;// ... 析构函数会释放 _data 指向的堆内存
};
void reserve(size_t n)
{			if (n > capacity()){size_t old_size = size(); T* tmp = new T[n];memcpy(tmp, _start, size() * sizeof(T));delete[] _start;_start = tmp;_finish = tmp + old_size;_end_of_storage = tmp + n;}
}

memcpy(tmp, _start, size() * sizeof(T)); ,当使用memcpy拷贝string对象时,只是按字节赋值string对象的成员变量,没有复制_data指向的堆上的字符内容。这会导致旧内存中的string对象和新内存的string对象,_data指针指向同一块堆内存。旧内存执行delete[] _start; 时,底层调用n次string的析构函数完成n个对象中资源的清理,再调用operator delete[ ]函数释放空间,新内存中的string对象的_data指针变成野指针(指向已释放的内存)。所以,后续访问这些新的string对象(如打印、再次析构)时,就会出现未定义行为(乱码、崩溃等)。

解决思路:修改reserve函数,把memcpy换成循环赋值:

void reserve(size_t n)
{			if (n > capacity()){size_t old_size = size(); T* tmp = new T[n];//memcpy(tmp, _start, old_size * sizeof(T)); 只适合内置类型,对于带动态内存管理的类(如string)memcpy执行浅拷贝会出错      for (size_t i = 0; i < old_size; ++i){tmp[i] = _start[i]; //tmp[i]和_start[i]都是string对象,对它们的赋值会触发string类的operator= 重载函数 ,相当于a.operator=(b)   string的赋值运算符会深拷贝堆上的字符}delete[] _start;_start = tmp;_finish = tmp + old_size;_end_of_storage = tmp + n;}
}

总结一下,不能用memcpy来拷贝自定义类型(尤其是带资源管理的类型,如string、vector自己等),必须用深拷贝(调用拷贝构造或赋值运算符),否则会导致多个对象共享同一份资源,引发资源管理错误(重复释放、野指针访问等),从而出现乱码等问题。

push_back("11111111")参数传递过程

void push_back(const T& x)

push_back的参数是const string& x,字符串字面量(类型为const char[N],会自动“衰减”为const char*)。因为std::string类提供了非explicit的构造函数string(const char*) 允许隐式类型转换,因此编译器会自动用这个构造函数把const char*隐式转换成string临时对象,然后将临时对象绑定到const string&参数上(const引用会延长临时对象生命周期,直到函数调用结束)。

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

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

相关文章

机器翻译:python库translatepy的详细使用(集成了多种翻译服务)

更多内容请见: 机器翻译修炼-专栏介绍和目录 文章目录 一、translatepy概述 1.1 translatepy介绍 1.1 安装 二、基本使用 2.1 初始化 `Translator` 2.2 文本翻译 2.3 语言检测 2.4 获取翻译备选方案 2.5 单词音标获取 2.6 语音合成 2.7 例句查询 2.8 拼写检查 三、高级功能 3.…

Spring Bean生命周期的完全指南

简介&#xff1a;超越Bean——揭开Spring Bean的隐秘生活 想象一场复杂宏大的舞台剧。作为观众&#xff0c;我们看到的是最终的演出——一个流畅运行的应用程序。但在这光鲜的幕后&#xff0c;隐藏着一套严谨细致的流程&#xff1a;选角&#xff08;实例化Bean&#xff09;、试…

网络安全A模块专项练习任务九解析

任务九&#xff1a;Linux操作系统安全配置-2任务环境说明&#xff1a; (Linux)系统&#xff1a;用户名root&#xff0c;密码1234561. 设置禁止使用最近用过的6个旧密码&#xff0c;将配置文件中对应的部分截图&#xff1b;编辑/etc/pam.d/system-auth文件&#xff0c;找到passw…

Linex进程管理

一、进程查看命令1.pstree用于查看进程树之间的关系&#xff0c;谁是父进程&#xff0c;谁是子进程&#xff0c;可以清楚的看出来是谁创建了谁语法&#xff1a;pstree [选项] -A各进程树之间的连接以ASCII码字符来连接-U各进程树之间的连接以utf8字符来连接&#xff0c;某些终…

手写MyBatis第47弹:Interceptor接口设计与Invocation上下文传递机制--MyBatis动态代理生成与方法拦截的精妙实现

&#x1f942;(❁◡❁)您的点赞&#x1f44d;➕评论&#x1f4dd;➕收藏⭐是作者创作的最大动力&#x1f91e; &#x1f496;&#x1f4d5;&#x1f389;&#x1f525; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4dd;欢迎留言讨论 &#x1f525;&#x1f525;&…

自动驾驶中的传感器技术37——Lidar(12)

这里对当前Lidar中的一些常见问题进行专项论述。首先以禾赛Lidar为例&#xff0c;列出相关参数&#xff0c;以备论述。 图1 禾赛AT128参数图2 禾赛AT360参数图3 禾赛AT1440参数图4 禾赛AT128可靠性验证项图5 禾赛AT128产品证书1、Lidar的线束是什么&#xff0c;由什么决定&…

Meteor主题友链页面自研

发布于&#xff1a;Eucalyptus-Blog Meteor主题虽然设计简约现代&#xff0c;但由于缺乏原生的友情链接管理功能&#xff0c;许多博主只能将友情链接勉强添加在网站底部&#xff0c;这不仅影响页面美观&#xff0c;也不便于访客查找和互动&#xff1b;为了解决这一痛点&#xf…

QT控件QPlainTextEdit、QTextEdit与QTextBrowser的区别

一.主要功能对比二.关键功能差异1.文本类型支持QPlainTextEdit&#xff1a;仅支持纯文本&#xff08;Plain Text&#xff09;&#xff0c;不处理任何格式&#xff08;如字体、颜色、链接、图片等&#xff09;。文本以原始字符形式存储&#xff0c;适合处理日志、代码、配置文件…

【思考】WSL是什么

WSL WSL是什么呢&#xff1f; WSL 是 windows subsystem for linux 的简写&#xff0c;指的是 windows10 的一个子系统&#xff0c;这个子系统的作用是在 windows 下运行 linux 操作系统。 有了WSL&#xff0c;就可以在 windows10 中运行linux操作系统了。许多在 linux 种运行的…

基于单片机智能饮水机/智能热水壶

传送门 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品题目速选一览表 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品题目功能速览 概述 基于单片机的智能饮水机系统通过嵌入式技术实现水温控制、水量监测及用户交互功能。系统采用STM3…

Unity游戏打包——iOS打包基础、传包

本文由 NRatel 历史笔记整理而来&#xff0c;如有错误欢迎指正。 相关参考文档 Unity文档 -> 平台开发 -> IOS https://docs.unity3d.com/cn/2021.3/Manual/iphone.html Unity导出的Xcode 项目的结构 Modifying an Xcode project use Xcode.PBXProject. https://doc…

pyside6小项目:进制转换器

from PySide6.QtUiTools import QUiLoader from PySide6.QtWidgets import QApplication,QWidgetclass MyWindow(QWidget):def __init__(self):super().__init__()self.ui QUiLoader().load(trans.ui)self.ui.show()#stor data type dictionaryself.lengthVar {米:100, 千米:…

再见 K8s!3款开源的云原生部署工具

前文&#xff0c;和大家分享了云原生中的核心工具 K8s&#xff1a; 关于 K8s&#xff1a;入门&#xff0c;这篇就够了 K8s是个好东西&#xff0c;就是上手门槛有点高。这不&#xff0c;需求就来了&#xff1f; 有需求&#xff0c;就有工具。 为了解决K8s的配置难题&#xf…

C++ 快速复习指南(上半部分)

1.基础语法基本结构#include <iostream> 头名 using namesapce std ; 统一使用命名空间 int main () { 程序执行门户 主题内容}基本输出 cout << "string " << endl; // 输出 string 变量和数据类型 格式int intger 10 ;常量的引入 需要在变量…

ArcGIS Pro 地图打包与解包

如果需要在ArcGIS Pro 打包某一个地图文档&#xff0c;在 菜单栏中 点击 共享&#xff0c;点击地图。弹出 打包地图 面板&#xff0c;可以打包到Online、打包到地图包&#xff0c;选择将包保存到文件&#xff0c;修改项目详细信息&#xff0c;点击 包&#xff0c;即可实现打包。…

sunset: twilight靶场

sunset: twilight 来自 <sunset: twilight ~ VulnHub> 1&#xff0c;将两台虚拟机网络连接都改为NAT模式 2&#xff0c;攻击机上做namp局域网扫描发现靶机 nmap -sn 192.168.23.0/24 那么攻击机IP为192.168.23.128&#xff0c;靶场IP192.168.23.145 3&#xff0c;对靶机…

【机器学习基础】无监督学习算法的现代演进:从数据探索到智能系统的自主发现能力

1. 引言:无监督学习在人工智能革命中的核心价值 在人工智能技术飞速发展的今天,无监督学习正在成为推动AI系统实现真正智能的关键技术。与需要大量标注数据的监督学习不同,无监督学习能够从原始数据中自主发现隐藏的模式和结构,这种能力使其在现代AI应用中具有不可替代的价…

PetaLinux的JTAG启动

csdn–PetaLinux 使用技巧与缓存配置 xilinx官网–PetaLinux 工具文档参考指南 (ug1144) xilinx官网–设备树配置文档 内核官网–设备树文档 软硬件准备 分类项目说明/用途验证方法示例硬件JTAG 线JTAG 下载、调试—UART 串口线查看 zynq 启动日志—网口线用于 TFTP 下载—…

单片机中的按键防抖

按钮&#xff08;按键&#xff09;抖动是单片机开发中常见的硬件问题&#xff0c;本质是机械触点接触瞬间的物理弹跳导致的电信号不稳定。消除抖动&#xff08;防抖&#xff09;是确保按键状态检测准确的关键&#xff0c;下面从原理到实现详细讲解。 一、按钮抖动的原理&#x…

面经分享--小米Java一面

目录 1.Kafka和RocketMQ的区别 2.反射的作用 3.类加载的具体过程&#xff0c;双亲委派模型的机制 4.TCP的四次挥手 5.多线程的优势 6.死锁产生的原因&#xff0c;怎么解决 7.Java并发的工作原理 8.常用的git命令 9.算法题 1.leetcode 3.无重复字符的最长子串&#xff…