C++之string的底层简单实现!(七千字长文详解)_c,2024年最新Golang开发基础课程

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

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

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

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

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

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

正文

文章目录

string之私有成员变量

namespace MySTL
{
    class string
	{
    private:
		char\* _str;
		size_t _size;
		size_t _capacity;
		//这里capacity表示的是实际可以存的内存的大小!
        //也可以表示真实的内存大小!
        const static size_t npos = -1;
	};
}

因为_size和 _capacity都不存在负数所以都是使用 size _t类型

size_t 是 unsigned long long 类的重命名!

本来c++的静态成员变量是不可以使用缺省值的!

但是c++对于整形类型开了一个后门!可以允许在整形静态成员变量的情况下使用缺省值!

string构造函数

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

错误的写法在初始化列表初始化数组!

string(const char\* str = "")
    :\_str(str),
	\_size(strlen(str)),
	\_capacity(strlen(str))
{
}//写法1

string(const char\* str = nullptr)//str = '\0'
{
    _size = strlen(str);
	_capacity = _size;
	_str = new char[_capacity + 1];
	strcpy(_str, str);
}
//写法2

写法1这样的写法有什么问题呢?

那就是_str这个指针它指向了的是一个常量字符串!而常量字符串位于常量区是不可修改的!所以我们就不可以对这个字符串进行增删查改!

而且这样的写法不太好看,可读性不强!

写法2 都会这样写会导致直接蹦,因为strlen遇到了空指针会直接崩溃!

使用’\0’和空指针也是一样的!,因为’\0‘ 的ASCII值为 0 也相当于空指针!

""  ------这是一个字符串,里面只有一个\0
'\0' ------这个一个字符
"\0" -------这也是一个字符串,里面有两个\0
//这三者要区分清楚!

string的拷贝构造

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

= 重载!

 string& operator=(const string& str)
{
	if (this == &str)
	{
		char\* temp = new char[str._capacity + 1];
		strcpy(temp, str._str);
        
		delete[] _str;//比起拷贝构造要多一步先把原来自己的空间释放掉!
		_str = temp;
        
		_capacity = str._capacity;
		_size = str._size;
	}
	return \*this;
}

string的析构函数

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

string的迭代器

typedef char\* iterator;

string类型的迭代器底层就是char*!

begin
iterator begin()
{
	return _str;
}

end
iterator end()//迭代器的最后一个指向有效数据的下一位!
{
	return _str + _size;
}

string的const迭代器

typedef const char\* const_iterator;

静态迭代器

begin
const_iterator begin()const
{
	return _str;
}

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(c);
}
//插入单个的字符,逻辑是根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(c);
	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

那如果我们把end的类型换做int这个有符号位是否能解决这个问题呢?

答案是不行!因为发生了整形提升!当左右运算符左右的两个类型相似的时候!为了确保精度!编译器会自动的将低精度的类型提升为高精度的类型!

image-20221108223247908.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

结果成功的插入了!

还有一种解决方法就是不让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

插入一个字符串

用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

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

一旦空间被释放,我们插入的值和原先的值都会清空!

image-20221110095446705.png

不仅打印错误还出现了崩溃!

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

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

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
数!导致了小于 0 之后变成了一个极大值!这个情况下无法进入循环刚刚好避免了上面的问题!



返回值一定要是string& 而不是 string!这样可以防止没有写拷贝构造的时候发生调用了两次析构用来释放同一块空间!

默认拷贝构造是浅拷贝!使用string作为返回值就会让临时产生变量中的_str指向同一块的空间!

image-20221109215325168.png

一旦空间被释放,我们插入的值和原先的值都会清空!

image-20221110095446705.png

不仅打印错误还出现了崩溃!

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

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

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

  • 23
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值