2024年C C++最新C++之string的底层简单实现!(七千字长文详解)_c,2024年最新看我如何一步一步攻克面试官

img
img

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

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

如果你需要这些资料,可以戳这里获取

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

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

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

<< 重载!

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

所这样写是错误的!

img
img

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

需要这份系统化的资料的朋友,可以添加戳这里获取

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

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

所这样写是错误的!

[外链图片转存中…(img-8tn1ZZh4-1715546296177)]
[外链图片转存中…(img-DJhSHOo5-1715546296177)]

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

需要这份系统化的资料的朋友,可以添加戳这里获取

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

  • 14
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值