C++之string的底层简单实现!(七千字长文详解)_c(1),面试一路绿灯Offer拿到手软

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Golang全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注go)
img

正文

}


#### end



const_iterator end()const
{
return _str + _size;
}


### size



size_t size()const
{
return _size;
}



> 
> 对于这种不需要对string类里面进行修改的函数接口我们都在后面加上const保证其更好的泛用性!
> 
> 
> 


### capacity



size_t capacity()const
{
return _capacity;
}


### []重载!



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

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



> 
> []重载要两个,因为[]既可以用来访问string类里面的值,也可以用来修改string类里面的值!
> 
> 
> 


### reserve



void reserve(size_t n)
{
char* temp = new char[n + 1];
strcpy(temp, _str);
delete[] _str;
_str = temp;
_capacity = n;
}



> 
> new 要开n+1个空间!是为了最后要装一个\0
> 
> 
> 然后将原来的数据拷贝到新的空间里面!释放旧空间!
> 
> 
> 然后将\_str 指向新空间!
> 
> 
> 


### resize



void resize(size_t n, char ch = ‘\0’)
{
assert(n >= 0);
if (n <= _size)
{
_str[n] = ‘\0’;
_size = n;
}
else if (n > _size && n <= _capacity)
{
while (_size < n)
{
_str[_size] = ch;
_size++;
}
_str[_size] = ‘\0’;
}
else
{
reserve(n);
while (_size < n)
{
_str[_size] = ch;
_size++;
}
_str[_size] = ‘\0’;
_capacity = n;
}
}



> 
> 三种情况!  
>  第一种 n < \_ size 直接删除数据!  
>  第二种情况!\_ size< n < capacity 填充数据!  
>  第三种情况! \_ capacity < n 扩容 + 填充数据!
> 
> 
> 


### 插入类接口!


#### Push\_Back



void Push_Back(char c)
{
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
_capacity = newcapacity;
}
_str[_size] = c;//这是\0的位置!
_size++;
_str[_size] = ‘\0’;//最后记得加上\0
}



> 
> 插入首先就要判断是否要扩容!一般都是选择二倍扩容!
> 
> 
> Push\_Back接口的错误写法!
> 
> 
> 
> ```
> void Push\_Back(char c)
> {
> 	if (_size == _capacity)
> 	{
> 		reserve(capacity\*2);
> 		_capacity \*= 2;
> 	}
> 	_str[_size] = c;//这是\0的位置!
> 	_size++;
> 	_str[_size] = '\0';//最后记得加上\0
> }
> 
> ```
> 
> 这样的写法有什么问题呢?那就是一旦capacity为0的话,那就无法扩容!
> 
> 
> 


#### append



void append(char c)
{
Push_Back©;
}
//插入单个的字符,逻辑是根Push_Back是一样的!
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
//strcat(_str, str);
strcpy(_str + _size,str);
//上面两种实现方式都可以!
//strcpy已经吧\0都拷贝到_str里面了
_size += len;
}
//插入一个字符串,也要进行扩容!
//但是不能二倍扩容!因为不能保证二倍扩容的空间大小是足够的!


#### +=重载



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


#### insert


##### 插入一个字符



> 
> insert是一个坑很多的接口接下来先给读者看一个错误的写法!
> 
> 
> 
> ```
> string& insert(size_t pos, char c)
> {
> 	if (_size == _capacity)
> 	{
> 		size_t newcapacity = _capacity == 0 ? 4 : _capacity \* 2;
> 		reserve(newcapacity);
> 		_capacity = newcapacity;
> 	}
> 	size_t end = _size;
> 	while (end >= pos)
> 	{
> 		_str[end + 1] = _str[end];
> 		end--;
> 	}
> 	_str[pos] = c;
> 	_size++;
> 	return \*this;
> }
> 
> ```
> 
> 这个写法看上去是没有什么问题的!但是其实,在第0位插入的时候会陷入死循环!
> 
> 
> 为什么?因为size\_t是一个无符号位!
> 
> 
> 一旦小于 0 就会变成一个极大的值!也就是说 永远无法小于 pos!
> 
> 
> ![image-20221108221438334.png](https://img-blog.csdnimg.cn/img_convert/0cc95308e343a64e1b6fcbf138ca6aee.png)
> 
> 
> **那如果我们把end的类型换做int这个有符号位是否能解决这个问题呢?**
> 
> 
> 答案是不行!因为发生了整形提升!当左右运算符左右的两个类型相似的时候!为了确保精度!编译器会自动的将低精度的类型提升为高精度的类型!
> 
> 
> ![image-20221108223247908.png](https://img-blog.csdnimg.cn/img_convert/548a5a3749335c969bbf4dfda63880a3.png)
> 
> 
> 所以其实实际上这个**表面上**是int类型的end变量,实际上是size\_t类型的!
> 
> 
> 就看到end好像就是不断的减下去不停止!
> 
> 
> **如果要使用int类型的end变量来解决这个问题!还必须将pos强转为int类型防止发生整形提升!**
> 
> 
> 
> ```
> string& insert(size_t pos, char c)
> {
> 	if (_size == _capacity)
> 	{
> 		size_t newcapacity = _capacity == 0 ? 4 : _capacity \* 2;
> 		reserve(newcapacity);
> 		_capacity = newcapacity;
> 	}
> 	int end = _size;
> 	while (end >= (int)pos)
> 	{
> 		_str[end + 1] = _str[end];
> 		end--;
> 	}
> 	_str[pos] = c;
> 	_size++;
> 	return \*this;
> }
> 
> ```
> 
> ![image-20221108223133913.png](https://img-blog.csdnimg.cn/img_convert/37d25892e4b94a413db7c830d8821d61.png)
> 
> 
> 结果成功的插入了!
> 
> 
> **还有一种解决方法就是不让end 小于 0**
> 
> 
> 就是让end的开始位置从 '\0’的下一位开始!
> 
> 
> 
> ```
> string& insert(size_t pos, char c)
> {
> 	if (_size == _capacity)
> 	{
> 		size_t newcapacity = _capacity == 0 ? 4 : _capacity \* 2;
> 		reserve(newcapacity);
> 		_capacity = newcapacity;
> 	}
> 	size_t end = _size + 1;
> 	while (end > pos)//不可以 >= 因为一旦 == 就会导致 end == 0 随后end-- 变成-1 其实是一个极大的值!
> 	{
> 		_str[end] = _str[end - 1];
> 		end--;
> 	}
> 	_str[pos] = c;
> 	_size++;
> 	return \*this;
> }
> 
> ```
> 
> ![image-20221108224020969.png](https://img-blog.csdnimg.cn/img_convert/aa15eea38a58b5d24755b00e2801bd31.png)
> 
> 
> 


##### 插入一个字符串



> 
> 用insert插入一个字符串的难点在于循环的范围!
> 
> 
> 下面就是一个经典的错误案例
> 
> 
> 
> ```
> string& insert(size_t pos, const char\* str)
> {
> 	size_t len = strlen(str);
> 	if (_size + len > _capacity)
> 	{
> 		reserve(_size + len);
> 	}
> 	size_t end = _size;
> 
> 	/\*while (end > pos)
>  {
>  \_str[end] = \_str[end - len];
>  end--;
>  }\*///循环1
>  /\*while (end >= pos)
>  {
>  \_str[end+len] = \_str[end];
>  end--;
>  }\*///循环2
> 
>  while (end > pos)
> 	{
> 		_str[end] = _str[end - len];
> 		end--;
> 	}//循环3
> 	strcpy(_str + pos, str);
> 	return \*this;
> }
> int main()
> {
> 	MySTL::string s1 = "hello";
> 	s1.insert(0, "hhh");
> 	cout << s1.c\_str() << endl;
> 	return 0;
> }
> 
> ```
> 
> 1. 首先是循环范围这个循环其实其实已经发生了越界访问!
> 
>  当end < len的时候 就会出现一个极大值!此时已经出现了越界的访问!
> 
> 
> [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VtyL3eRM-1668311036147)(https://s2.51cto.com/images/202211/89142d780c3b6eb765c052871abc7a40b89a22.png?x-oss-process=image/watermark,size\_14,text\_QDUxQ1RP5Y2a5a6i,color\_FFFFFF,t\_30,g\_se,x\_10,y\_10,shadow\_20,type\_ZmFuZ3poZW5naGVpdGk=)]
> 
> 
> 2 . 第二种循环的问题就是和上面的一样一旦遇到pos = 0 的位置的时候就会进入死循环!解决的方法也是一样的!就是将end类型换成int 将pos强转为int
> 
> 
> 
> ```
> int end = _size;
> while (end > (int)pos)
> {
> 	_str[end] = _str[end - len];
> 	end--;
> }
> 
> ```
> 
> 3.第三种循环会出现!会出现越界访问!当end < len的时候!end -len就会变成一个极大的值!
> 
> 
> 4 . 然后是strcpy,因为拷贝的过程中将\0也一起拷贝进去了,所以一旦打印出结果就只有插入的值,而插入后面的值都无法显示!
> 
> 
> ![image-20221108230044881.png](https://img-blog.csdnimg.cn/img_convert/08228fca7e43441f5fab7ab70c82bc7f.png)
> 
> 
> insert的正确写法
> 
> 
> 
> ```
> string& insert(size_t pos, const char\* str)
> {
> 	size_t len = strlen(str);
> 	if (_size + len > _capacity)
> 	{
> 		reserve(_size + len);
> 	}
> 	size_t end = _size + len
>         ;//从最后的\0的位置开始!
> 	while (end > pos + len - 1)
>         //如果不pos +len-1 会导致少移动一个字符!
> 	{
> 		_str[end] = _str[end - len];
> 		end--;
> 	}
> 	strncpy(_str + pos, str, len);//不可以把str的\0也拷贝进去!
> 	_size += len;
> 	return \*this;
> }
> 
> ```
> 
> 
> > 
> > 
> > ```
> > while (end > pos + len - 1);
> > //如果改成
> > while (end >= pos + len);
> > //当pos == 0 ,len == 0 ,end == 0的时候!就会进入死循环!
> > //因为end-- 变成一个极大的值!一直死循环!
> > 
> > ```
> > 
> > [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4bWqpyw2-1668311036148)(https://s2.51cto.com/images/202211/c399897028905a59197927d2b454ed71afc128.png?x-oss-process=image/watermark,size\_14,text\_QDUxQ1RP5Y2a5a6i,color\_FFFFFF,t\_30,g\_se,x\_10,y\_10,shadow\_20,type\_ZmFuZ3poZW5naGVpdGk=)]
> > 
> > 
> > 
> > ```
> > while (end > pos + len - 1);
> > //这个条件循环在end == 0 ,pos == 0,len == 0 的条件下!
> > //因为 pos + len — 1 也是被整形提升了!所以结果是一个无符号数!导致了小于 0 之后变成了一个极大值!这个情况下无法进入循环刚刚好避免了上面的问题!
> > 
> > ```
> > 
> > 
> 
> 
> 
> > 
> > **返回值一定要是string& 而不是 string!这样可以防止没有写拷贝构造的时候发生调用了两次析构用来释放同一块空间!**
> > 
> > 
> > **默认拷贝构造是浅拷贝!使用string作为返回值就会让临时产生变量中的\_str指向同一块的空间!**
> > 
> > 
> > ![image-20221109215325168.png](https://img-blog.csdnimg.cn/img_convert/c3daf5a1d8861ebead938d27f2421347.png)
> > 
> > 
> > 一旦空间被释放,我们插入的值和原先的值都会清空!
> > 
> > 
> > ![image-20221110095446705.png](https://img-blog.csdnimg.cn/img_convert/7e3af7cc7b27f4d711dd5dbcf191a2e0.png)
> > 
> > 
> > 不仅打印错误还出现了崩溃!
> > 
> > 
> > 
> 
> 
> 


### erase



string& erase(size_t pos, size_t len = npos)
{
size_t end = _size;
if (len == npos || len >= _size - pos)
{
_str[pos] = ‘\0’;
_size = pos;
}//当len不传值的时候,和len大于剩下空间的时候!
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}



> 
> **返回值一定要是string& 而不是 string!理由和上面一样都是为了防止二次释放!**
> 
> 
> 


### find



size_t find(char ch, size_t pos = 0)const
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
size_t find(const char* str, size_t pos = 0)const
{
const char* ptr = strstr(_str + pos, str);//要加pos是因为从pos的位置开始找的!
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}

}


### clear



void clear()
{
_str[0] - ‘\0’;
_size = 0;
}


### >>的重载!



ostream& operator<< (ostream& out, const string& str)
{
for (size_t i = 0; i < str.size(); i++)
{
out << str[i];
}
return out;
}



> 
> 流插入的重载相比以前的对于c的字符串的打印有个特点就是不以\0作为结尾!
> 
> 
> ![image-20221110110958246.png](https://img-blog.csdnimg.cn/img_convert/ada39712beaf1038c084288313135726.png)
> 
> 
> 


### << 重载!



istream& operator >>(istream& in, string& str)
{
str.clear();
char ch;
in >> ch;
while (ch != ‘\n’ && ch != ’ ')
{
in >> ch;
str += ch;
}
str += ‘\0’;
return in;
}



> 
> 这样写看上去没有问题!但是其实这样写会进入死循环!
> 
> 
> 这是因为cin将空格和换行当做是多个字符串之间的间隔!所以cin 是拿不到 空格和 \0
> 
> 
> 所这样写是错误的!
> 
> 
> 


**正确写法!**



istream& operator >>(istream& in, string& str)
{
str.clear();
char ch;
ch = in.get();
while (ch != ‘\n’ && ch != ’ ')
{
str += ch;
ch = in.get();
}
return in;
}



> 
> **in.get(),就相当于c语言中的getchar!可以提取空格和换行!**
> 
> 
> 但是这个代码有一个不好的一点!
> 
> 
> 一旦我们输入一串及其长的字符!那么就会频繁扩容!这样会导致不必要的性能损失!
> 
> 
> 所以我们可以继续改进一些!
> 
> 
> 


最终优化版!



istream& operator >>(istream& in, string& str)
{
str.clear();//用来清空str保留的数据!
char buff[128] = { ‘\0’ };
char ch;
ch = in.get();
int i = 0;
while (ch != ‘\n’ && ch != ’ ')
{
if (i == 127)//留一个用来存放\0 如果i == 128的话!会导致乱码!因为+=的底层是append,append是调用strcpy来实现的!strcpy是以\0作为结尾的!
{
i = 0;
str += buff;
}
buff[i++] = ch;
ch = in.get();
}
if (i > 0)
{
buff[i] = ‘\0’;//如果不加上这个的话会把后面原本的值都拷进去!
str += buff;
}
return in;
}
//getline的实现原理就是把条件换成while (ch != ‘\n’)



> 
> 将要输入的长字符串分割成一个个小段!减少扩容的次数!
> 
> 
> 


## 最终代码



namespace MySTL
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
const_iterator begin()const
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator end()const
{
return _str + _size;
}

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

	~string()
	{
		_size = _capacity = 0;
		delete[] _str;

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

namespace MySTL
{
	class string
	{
	public:
		typedef char\* iterator;
		typedef const char\* const_iterator;
		iterator begin()
		{
			return _str;
		}
		const_iterator begin()const
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		const_iterator end()const
		{
			return _str + _size;
		}

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

		~string()
		{
			_size = _capacity = 0;
			delete[] _str;


**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)**
[外链图片转存中...(img-rIekf4di-1713706438282)]

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
  • 18
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值