C++ STL初阶(2):string 的模拟实现

news2024/11/19 7:40:45

此文的背景是自己实现库中的string,由于string的模版实现较为困难,我们只实现最简单char版本。

1.命名空间分割

为了避免与库中的string冲突,我们使用一个自己的命名空间中来分离并实现所有内容,并且将所有的声明和定义相分离,因此需要使用相同名字的命名空间。申明和定义都在同一个命名空间中,就会自动合并。

         

                                                   (用一个命名空间来隔离。避免冲突)

为什么 # include " string.h"不会与库冲突?

在编译一讲提到过,双引号下的头文件名字先搜索自己的本地文件。又由于我们自己实现了

string.h在项目文件夹中,所以会优先使用我们自己实现的string


2.构造、析构函数

先在.h文件中声明:

                            

(VS下的string其实还包含一个16个字节的buffer数组,此处我们简化掉该buffer数组)

再到.cpp中去实现:

namespace lsnm {
	string::string(const char* str) 
		:_str(str),
		_size(strlen(str)),
		_capacity(strlen(str))
	{}
	string::~string() {

	}
}

为什么不用sizeof而是用strlen?

szieof(_str)相当于计算一个指针的大小,因为这是一个常量字符串的指针,而不是数组名,因此不会计数整个数组的大小。 

  但是发现出现了报错:

为什么在初始化列表中不能直接用参数str来初始化?

由内存管理中的知识可知,如果用一个常量字符串赋值来初始化

(str和_str都是char* 类型的变量,我们没有拷贝,而是一直都在传指针,相当于传了一个常量区的指针去可读可写,扩大了权限)

常量字符串是存在于常量区的并且不可被修改的,直接

:_str(str),

 会使我们按照string s1="abcd"初始化的s1无法改变内容(无法插入删除修改等)

正确使用方法:

namespace lsnm {
	string::string(const char* str) 
		:_str( new char[strlen(str)+1]),
		_size(strlen(str)),
		_capacity(strlen(str))
	{
		strcpy(_str, str);
	}
	string::~string() {

	}
}

提醒:类函数定义需要指定类域,所以每一个函数前面都有一个 string ::

   此时的函数有一个问题:      

上文构造函数中,strlen要跑三次,效率较低,能不能按照下文方法写?

先在初始化列表中写size,只执行一次strlen呢?

这是经典错误。因为初始化列表会按照在private中的声明的顺序初始化。

解决方案:

1.在private中改声明顺序为适合的顺序:

           

但是这样不妥,如果一不小心改了private中的顺序就会出现报错。

解决方案2:

初始化列表虽然好,但是也不能死板的一直使用,此时就建议放在函数体中去定义。

复习关于初始化列表:

string::string(const char* str) 
	:_size(strlen(str))
{
	_str = new char[strlen(str) + 1];
	strcpy(_str, str);
	_capacity = _size;
}

我们再快速实现一个c_str(因为现在还没有实现流提取的重载,所以c_str之后可以便于打印和检查),建议用后置const修饰this指针,也就是:

const char* c_str() const;

        这样的话const的string和非const修饰的string就都可以调用这个c_str() ,当然,同时也都不能修改由c_str返回的char形数组。

(此处的string都指的是我们自己实现的string)

注意:在.c文件中分离实现时,返回类型是写在域名的前面的。

           

namespace lsnm {
	string::string(const char* str) 
		:_size(strlen(str))
	{
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
		_capacity = _size;
	}
	string::~string() {
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}
	const char*  string :: c_str() const {
		return this->_str;
	}
}

在之前的学习中,我们说到构造函数(尤其是包含自定义类型)最好实现默认构造。

因此我们再实现一个无参的string构造函数。

                                            

这样写又是很经典的错误。错误原因:与库中的功能不符合。库中直接string s1;

紧接着,s1可以被自由使用、打印,其里面只包含了一个'\0'

但是报错的原因不是delete, 因为free和delete的底层都是可以操作nullptr的.....

正确的做法:

  (new出来的自定义类型在后面用花括号赋值):

string::string() {
	_str = new char[1] {'\0'};
	_size = _capacity = 0;
}

//也可以写成这样
string::string() 
	: _str(new char[1]{""})
{
	_size = _capacity = 0;
}

然后合并无参和带参为全缺省:

                       

           

不写\0是因为作为char类型的数组,本身自带\0

声明和定义分离时,参数写在声明处。


3.方括号遍历与size函数

size_t string :: size() const{
	return this->_size;
}

char& string:: operator[](size_t pos) {
    assert(pos<_size);
	return _str[pos];
}

const char& string :: operator[](size_t pos) const {
    assert(pos<_size);
	return _str[pos];
}

4.实现用于遍历的迭代器

除了方括号遍历, 最常用的还有范围for循环。

我们如果想实现范围for,就需要先实现迭代器版本的遍历。(范围for循环的底层是编译为迭代器版本的循环)

因为范围for的底层是迭代器;

我们此处只实现原生指针版本的迭代器

先typedef一下

注意,返回类型和函数名都属于类域中,都需要单独用类域展开一下:

                    

iterator属于 char* ,所以此处的实现直接按照指针来就可以了

string::iterator string::begin() {
	return this->_str;
 }
string::iterator string::end() {
	return this->_str + _size;
}

我们操作的都是加了一层皮的char*   , begin()和end()返回的都是指针

切记,iterator是我们自己定义的。


但是,倘若我们把自定义的iterator全部换回char*

范围for还能通过吗?

答案是可以的,因为范围for的底层是去找begin()和end(),只要实现了begin()和end(),就都可以实现了。auto又能自动推导类型,将e作为char类型

但是如果我们把begin改成Begin,范围for就又不能通过了,因为找不到begin()

iterator的作用:

用iterator的方法是完成一种对底层逻辑的封装。因为iterator其实不确定到底是哪种类型,自定义类型还是内置类型都有可能作为iterator。

因为iterator的原生类型都不一样,不同的平台实现也可能不一样,所以规定都叫做iterator,便于使用。比如reverse算法函数,不关心你的访问方法是自定义、还是char*、还是int*,只管使用iterator

这样就能将分离的算法和数据结构相结合,也统一了不同的数据结构的使用方法。

所有的访问方式都能通过迭代器进行。对使用者更加方便。

这一点也能体现类和对象中的特点之一:封装。


除此之外,还有const修饰的iterator:

      

string ::iterator string:: begin(){
	return _str;
}
string::iterator string::end(){
	return _str+_size;
}
string::const_iterator string::begin() const{
	return _str;
}
string::const_iterator string::end() const {
	return _str+_size;
}

5.增添、删除、修改 

string作为一个相对复杂的顺序表

5.1 push_back和append

前者用来插入字符,后者用来插入字符串。

前者在扩容时可以直接扩二倍,但是增加字符串的时候可以只增加二倍吗?

因此,我们需要先引入扩容函数:

             

实现如下:

void string :: reserve(size_t n) {
	if (n <= this->_size) return;
	char* tmp = new char[n+1];//多开一个预留给\0
	strcpy(tmp, _str);
	delete[] _str;
	_str = tmp;
	_capacity = n;
}

只要我们希望reserve出的空间大于等于 _size+1 (还需要给'\0'留一个位置), 就都是合法的,可以在大于等于_size+1的情况下进行缩容。 

再实现两个填充内容的函数: 

关于开出空间的大小:如果需要n个空间,永远开n+1个空间,因为要预留一个给\0

自己开空间,拷贝内容,改变指针指向,再将原空间释放掉。

同时,对push_back和append是否需要扩容作出判断:

    

在push_back汇总同时处理\0:

对于append,我们可以使用最简单的for循环一个一个放进去:

void string::append(const char* str) {
	size_t len = strlen(str);
	reserve(_size + len);
	for (int i = 0; i < len; i++) {
		_str[_size++] = str[i];
	}
	_str[_size] = '\0';
}

  

也可以用C语言中的字符串函数如strcat去实现,(strcat能自主覆盖destination的\0并且移植新的\0)

strcat有什么弊端?

strcat的底层是从头开始找'\0',然后从\0的位置开始覆盖。这样固然没有问题,但是操作效率变低。我们清晰\0的位置  :   (_str+_size) ,那直接使用strcpy,跳过寻找\0的过程,提高效率。

                 

再实现类似功能(并且最好用)的 += 

string& string::operator+=(char ch) {
	push_back(ch);
	return *this;
}

string& string::operator+=(const char* str) {
	append(str);
	return *this;
}

 此处是实现类的内部函数,所以默认所有的push_pack或者append都是直接对this对应的元素使用。

记得传引用返回,提升效率,避免传值返回时重复复制。

5.2 insert和erase

先声明三个函数:

             

为什么不给pos加缺省参数?

要给pos加缺省参数就必须先给ch或者str加,因为缺省参数只能从右边开始赋值。

需要用到npos,我们自己定义一个static的npos.

static需要在第一次使用时就既声明又定义,但如果就像上图那样使用,string.cpp和string.h都会包含一次这个npos,导致重复定义,从而链接出错。

关于静态成员在不需要链接时候的使用如下:

C++:类与对象(2)-CSDN博客

static修饰的成员变量没有被保存在类中,而是保存在静态区中。

本文中,应当将npos的声明和定义相分离:在.h中声明,在.cpp中定义 ,在.test中可以直接使用。

链接时,类似于函数一样,因为先在头文件中声明过了所以编译能通过,最后去生成.o中找这个值。

                        

补充一点很奇怪的知识:

可以用const修饰之后给缺省值,并且只有整型可以

很奇怪,了解即可。


实现insert:

                 注意,要将\0一并移走,所以从end+1(\0所在位置)开始移动。

void string :: insert(size_t pos, char ch) {
	assert(pos <= _size);
	if (_size == _capacity) {
		size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
		reserve(newcapacity);
	}
	size_t end = _size;
	for (int i = _size - pos; i >= 0; --i) {
		_str[end + 1] = _str[end];
		end--;
	}
	_str[pos] = ch;
}

注意顺序表阶段的一个小问题,往哪边挪就得从哪边开始挪。

不用担心\0,\0也被一起挪动了。


不过如果不引进变量i, 写成以下形式 ,并且进行头插(在pos=0的位置插入):

end作为一个无符号整形,不管如何加减,是不可能小于0的。

因此此时无法头插,会死循环:

                                              

那如果将end改为int呢?

依然死循环。

5.2.1无符号小于等于零都是坑

为什么将end的类型改成int之后依然会死循环呢??

对于一个双目操作符,当两侧数据的类型不一样时,会发生隐式类型转换。

其中的原则就是有符号的都会变为无符号的。因此在判断end >= pos时,会因为_size的类型是无符号整形,所以end也会被转换成无符号整形。

解决方法:

1.  while的条件中进行强转。

 2.  pos直接写成int,但是这样与库中不一样。

3.   将end指向更后面的一位,等于0的时候就会跳出循环。

4.   引入新变量int

                           


插入字符串的insert:

移动部分的逻辑同上:

紧接着我们利用库函数将传入的参数str直接插入*this

但是插入部分不能用strcpy,因为strcpy会自己补/0,提前结束字符串

因此使用strncpy或者memcpy来避免自动补\0的问题

关于C语言字符串函数中的弊端,我们稍加总结:strcat会从头开始找\0,效率较低;而strcpy会在插入结束后自动在末尾补\0,因此strcpy不能用于在一个字符串的中间插入;memcpy就是一个字节一个字节的拷贝,非常“朴实无华”) 

void string :: insert(size_t pos, const char* str) {
	assert(pos <= this->_size);
	size_t len = strlen(str);
	if (_capacity < _size + len)
		reserve(_size + len);

	size_t end = _size;
	//_str[_size + len + 1] = '\0';
	//"abcdefg\0"  "qwe\0"
	while (end >= pos) {
		_str[end + len] = _str[end];
		--end;
	}
	memcpy(_str + pos, str, len);
	_size += len;
}

依然有死循环的问题,我们改变end的类型并且在while条件处强转:

  


5.2.2erase

当触发len==npos,需要全部删完的时候:

             

不要考虑用delete,因为delete不能只删除部分空间。

直接将pos位置变成\0即可(pos位置后面的都不要了),同时改变_size

或者要删除的长度len大于可以被删除的部分(就像官网定义中的is too short的那样)


不全部删完:

直接平移覆盖即可。

void string::erase(size_t pos, size_t len) {
	assert(pos < _size);
	if (len > _size - pos) {
		_str[pos] = '\0';
	}
	else {
		for (int i = pos + len; i <= _size; i++) {
			_str[i - len] = _str[i];
		}
	}
}

也可以将for循环的覆盖写为:

                  


6.find函数

寻找字符:

默认找不到的时候不可能是无符号整形的最大值(42亿多,一个字符串不可能有四个G)

size_t string::find(char ch, size_t pos) const {
	assert(pos < _size);
	for (int i = pos; i < _size; i++) {
		if (_str[i] == ch)
			return i;
	}

	return npos;
}

匹配子串:

用strstr(底层是BF算法)即可。因为KMP的算法在实际运用中效率并没有非常出色,非常依赖自身的重复性(需要自身的重复性来体现效率)。


7.拷贝构造

      如果我们执行这样一个代码:               ​​​​

由于没有实现拷贝构造,所以自动生成一个浅拷贝。

但是此处浅拷贝就会在析构的时候报错,因为对同一块堆上的数组空间析构了两次。

同时,也存在修改s1就会修改到s2的尴尬情况。

因此,我们换一个新逻辑:直接重新开一个一样的空间,进行strcpy即可。

string::string(const string& s)
{
	_str = new char[s._capacity + 1];
	strcpy(_str, s._str);
	_size = s._size;
	_capacity = s._capacity;
}

8.运算符重载

8.1 赋值运算符重载:

先试试系统默认生成的:

为什么发生报错呢?

原因和之前的拷贝构造一样,默认生成的是浅拷贝,浅拷贝是一个字节一个字节的拷贝,会将_str的指针拷贝过去,在释放时会对同一个数组delete[]两次,因此报错。

除了两次delete[]会发生报错,还有以上两种情况证明浅拷贝是不够的: 

情况1空间不够,情况2空间浪费严重

解决方法:

我们简单粗暴的处理,直接开一个新空间调用strcpy,再释放掉原空间。     

不过倘若执行 s1=s1就亏了,再加一个判断条件:

此时再执行s2=s1就不会报错了:


 此时能使用swap完成s1和s2的交换吗?

8.2 swap

答案是可以的,因为swap是模版函数。

但是这个swap代价很大,通过观察swap的源码,我们发现要完成swap需要三次深拷贝。

所以我们自己在类中实现一个消耗小的:

直接改指针即可,并且使用库中的swap调换相应的数据:

            


    C++标准库自然也想到了这个问题,string作为一个容器,有专属于自己的swap,来避免深拷贝问题。

    为了避免使用者不小心调用库中的标准swap,c++考虑的非常周全,利用匹配原则(如上):直接调用swap(string)版本,并且还是全局实现的。

"the strings exchange references to their data, without actually copying the characters "

只交换指针,没有交换实质里的内容。


9.substr

       获取子串依然是一个涉及“len和pos”的问题,依然分两种情况讨论。

​​​​​​​​​​​​​​

                                        (直接从pos的位置去构造一个)

会报错,此处的问题在于不能传引用返回,因为sub会被销毁。

string string::substr(size_t pos, size_t len) {
	if (pos + len >= _size) {
		string sub(_str + pos);
		return sub;
	}
	else {
		string sub;
		sub.reserve(_size + 1);
		for (int i = 0; i < len; i++) {
			sub += _str[pos + i];
		}
		return sub;
	} 
}


10.其他常用运算符

如+ - += -=等等

我们借助strcmp来实现小于和等于。

实现小于和等于之后·,其他都可以直接复用。

bool string::operator==(const string& s) {
	return strcmp(_str, s._str) == 0;
}
bool string::operator<(const string& s) {
	return strcmp(_str, s._str) < 0;
}
bool string::operator<=(const string& s) {
	return (*this < s) || (*this == s);
}
bool string::operator>(const string& s) {
	return !(*this<=s);
}
bool string::operator>=(const string& s) {
	return (*this > s) || (*this == s);
}
bool string::operator!=(const string& s) {
	return !(*this == s);
}

11.流插入和流提取

由于运算符中操作数顺序的问题(cout<<s1),流插入不适于写在类内部(类内部函数的第一个参数是this)。

但是此处不需要写成友元函数,因为可以不访问类内部的数据(访问一个_size和使用一个public函数 operator[ ]),就可以直接访问公有元素。

因为[ ]运算符在重载之后的本质是一个返回char&的函数,而该函数是在public中的,所以可以直接使用

最后return的目的是为了便于连续输出。

留提取:

我们此时只输入4个x试试:

这是因为只提取了一次,,,,

需要多次提取:

依然拿不到换行。

看看测试函数:

                

因为cin拿不到空格和换行。cin会将空格和换行默认当作操作者这次输入与下次输入之间的隔阂。

正确使用(能拿到换行):用is.get()

还需要一个clear,cin之前的要清空。

综上所述,

流插入和流提取不能写作成员函数   (正确)

流插入和流提取需要写成友元函数   (错误)

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

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

相关文章

Three.js-实现加载图片并旋转

1.实现效果 2. 实现步骤 2.1创建场景 const scene new THREE.Scene(); 2.2添加相机 说明&#xff1a; fov&#xff08;视场角&#xff09;&#xff1a;视场角决定了相机的视野范围&#xff0c;即相机可以看到的角度范围。较大的视场角表示更广阔的视野&#xff0c;但可能…

P3. 创建个人中心页面

P3. 创建个人中心页面 0 概述Tips1 个人中心页面1.1 创建 Bot 表及 pojo, mapper1.2 实现 Bot 增删改查的 API1.3 实现个人中心页面前端 0 概述 主要介绍了一下添加一个表(类)&#xff0c;及其CRUD的前端和后端的实现方式&#xff0c;介绍的是通用的方法。 后端的CRUD很好写&am…

【Java面试】十五、HashMap相关

文章目录 1、二叉树1.1 二叉搜索树1.2 红黑树 2、散列表2.1 哈希冲突2.2 哈希冲突 - 链表法 3、HashMap的实现原理4、HashMap源码4.1 属性部分4.2 构造函数部分 5、HashMap的put方法的流程6、HashMap的扩容机制7、HashMap的寻址算法8、为何HashMap底层的数组长度一定是2的次幂 …

导入地址表钩取技术解析

前置知识 导入表 在一个可执行文件需要用到其余DLL文件中的函数时&#xff0c;就需要用到导入表&#xff0c;用于记录需要引用的函数。例如我们编写的可执行文件需要用到CreateProcess函数&#xff0c;就需要用到kernel32.dll文件并且将其中的CreateProcess函数的信息导入到我…

数据库管理-第198期 升级Oracle ACE Pro,新赛季继续努力(20240605)

数据库管理198期 2024-06-05 数据库管理-第198期 升级ACE Pro&#xff0c;新赛季继续努力&#xff08;20240605&#xff09;1 惊喜2 变化3 Oracle ACE总结 数据库管理-第198期 升级ACE Pro&#xff0c;新赛季继续努力&#xff08;20240605&#xff09; 作者&#xff1a;胖头鱼的…

【PCB]射频电路pcb设计

学习改变命运&#xff0c;技能成就未来&#xff01;❤~~ 1射频信号的基础知识及工作原理介绍 射频的基础知识介绍 2射频板PCB的布局要求 3射频板布局要求 4屏蔽帐设计 5射频板的层叠阻抗设计 6射频板的PCB布线原则 7射频板的PCB布线要求 8射频板的设计实战

kubeedge v1.17.0部署教程

文章目录 前言一、安装k8s平台二、部署kubeedge1.部署MetalLB(可选)2.cloud3.edge4. 部署nginx到edge端 总结参考 前言 本文主要介绍kubeedge v1.17.0的安装过程 主要环境如下表 应用版本centos7.0k8s1.28.2kubeedge1.17.0docker24.0.8centos7.0 一、安装k8s平台 本文主要参…

2024年6月1日 (周六) 叶子游戏新闻

Embracer探讨单机游戏大作涨价超过70美元的可能性在Embracer集团等待公布新公司名称的同时&#xff0c;他们对游戏大作的价格上涨做出了评论。几年来&#xff0c;游戏大作的价格已经达到了70美元的门槛。Embracer集团的CEO Lars Wingefors在采访中表示&#xff0c;电子游戏行业…

MySQL—多表查询—内连接

一、引言 &#xff08;1&#xff09;内连接查询语法 内连接查询的是两张表的交集部分的数据。&#xff08;也就是绿色部分展示的数据&#xff09; &#xff08;2&#xff09;内连接有两种形式&#xff1a; 1、隐式内连接 语法结构&#xff1a; 2、显示内连接 语法结构&#xf…

AIGC绘画设计——midjourney有哪些好用的关键词?

midjourney有哪些高级关键词&#xff1f; 这一期继续分享一些高级的关键词&#xff0c; 我有一些案例也是从其他博主那学习来的&#xff0c; 但为了尽可能不出错&#xff0c;每个案例都是自己尝试了很多次后才拿出来的。 挑选了几个效果比较好&#xff0c;使用场景较高的类型…

lux和ffmpeg进行下载各大主流自媒体平台视频

1、lux下载&#xff0c;链接&#xff1a;https://pan.baidu.com/s/1WjGbouL3KFTU6LeqZmACpA?pwdagpp 提取码&#xff1a;agpp 2、ffmpeg下载&#xff0c;跟lux放在同一个目录&#xff1b; 3、为lux、ffmpeg设置环境变量&#xff1b; 4、WINR&#xff0c;打开运行&#xff0…

手眼标定学习笔记

目录 标定代码&#xff1a; 手眼标定原理学习 什么是手眼标定 手眼标定的目的 eye in hand eye to hand AXXB问题的求解 标定代码&#xff1a; GitHub - pumpkin-ws/HandEyeCalib 推荐博文&#xff1a; https://zhuanlan.zhihu.com/p/486592374 手眼标定原理学习 参…

nexus搭建npm前端项目的私服

一、为什么要搭建私库 节省外网带宽加速maven构建部署第三方构件&#xff08;特别是无法从公共仓库下载的构件&#xff09;提高稳定性&#xff08;内网部署&#xff0c;更少地依赖外网&#xff09;降低中央仓库的负荷 构件&#xff0c;好比我们的藏书&#xff0c;去书店或商城…

【数据库】SQL--DDL(初阶)

文章目录 DDL1. 数据库操作1.1. 表操作1.1.1 创建1.1.2. 查询 2. 数据类型及案例2.1 数值类型2.2 字符串类型2.3 日期时间类型2.4 案例练习 3. 表操作--修改3.1 添加字段3.2 修改字段3.3 修改表名 4. 表操作-删除4.1 删除字段4.2 删除表 5. DDL小结 更多数据库MySQL系统内容就在…

如何在强数据一致性要求下设计数据库的高可用架构

在高可用的三大架构设计(基于数据层的高可用、基于业务层的高可用,以及融合的高可用架构设计)中。仅仅解决了业务连续性的问题:也就是当服务器因为各种原因,发生宕机,导致MySQL 数据库不可用之后,快速恢复业务。但对有状态的数据库服务来说,在一些核心业务系统中,比如…

svn的使用

【图文详解】入职必备——SVN使用教程-CSDN博客 使用SVNBucket作为服务端,来辅助学习. 什么时候会产生冲突呢? 原本A,B,服务器的版本都一致,都是最新版. A修改文件m,向服务器提交 B修改文件m,向服务器提交,这时候出现了冲突 双击冲突的文件,手动修改

鬼畜恶搞类型的视频素材哪里找?热门搞笑素材网站分享

在当今数字媒体时代&#xff0c;寻找优质的视频素材变得尤为重要&#xff0c;尤其是对于喜欢鬼畜恶搞风格的创作者来说&#xff0c;选择合适的素材网站可以大大提升视频的吸引力和观看体验。本文将为短视频创作者和自媒体运营者介绍一些顶级的视频素材网站和工具&#xff0c;特…

方案设计|汽车轮胎数显胎压计方案

一、引言 数显轮胎胎压计是一个专门测量车辆轮胎气压的工具&#xff0c;它具有高精度测量的功能&#xff0c;能够帮助快速准确获取轮胎气压正确数值&#xff0c;保证轮胎使用安全。本文将对数显轮胎胎压计的方案技术进行分析&#xff0c;包括其基本原理、硬件构成、软件设计等方…

nvme-cli常见命令分析

一、背景 nvme-cli命令常常用于获取或者设置SSD参数&#xff0c;比如常见的nvme list&#xff0c;nvme id-ctrl等&#xff0c;都是获取SSD的基本信息&#xff0c;也有nvme admin-passthru用于读取或者设置自定义命令。作为使用者&#xff0c;我们并不知道nvme-cli源码怎么实现…

k8s怎么监听自定义资源的变更?(2)

接上一篇当生成下面代码之后怎么去使用呢&#xff1f; 1.生成crd文件 这里我们通过kubebuilder的一个子项目 controller-gen 来生成crd文件 https://github.com/kubernetes-sigs/controller-tools curl -L -o https://github.com/kubernetes-sigs/controller-tools; go ins…