【C++第八课 - string的底层实现】

基础知识

capacity:指可以存储有效字符的个数,不包含/0
size:指现有的有效字符个数,不包含/0

string构造函数和析构函数的坑

构造函数

1、重复使用strlen()造成效率降低
在这里插入图片描述
上述构造函数中使用的strlen(s)的时间复杂度为(O(n),没算一次都要从头数到尾)
为了解决上述问题,提出了下面的解决方法
在这里插入图片描述
上述解决方法是不对的,这样声明和初始化列表的顺序必须一致

	private:
		size_t _size;
		size_t _capacity;
		char* _str;

上述解决方法的不足:如果动了声明的顺序或初始化列表的顺序就会报错
我们可以不用初始化列表,直接在里面构造就好了
在这里插入图片描述
2、无参的构造函数
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们写了上述的无参构造函数,但这样写是存在问题的。因为在cout的时候要对c_str()返回的char*进行解引用,而无参时返回的是空指针,这也就造成了空指针解引用问题

在这里插入图片描述
但是平时写构造函数的时候也没有两个分开写的时候,所以要把两个合起来
在这里插入图片描述
上述还有个问题s的内容没有copy给_str

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

析构函数

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

迭代器、范围for

		typedef char* iteractor;
		iteractor begin()
		{
			return _str;
		}
		iteractor end()
		{
			return _str + _size;
		}

在这里插入图片描述
范围for的底层实际上就是迭代器,他只会傻瓜式的替换成迭代器
如果把我们string里面的begin换掉Begin,那么范围for就会无法替换,产生报错。
在这里插入图片描述

在这里插入图片描述

运算符重载

operator []

在这里插入图片描述

const

const char* a这表示指针指向的内容不能被修改
char* const a这里表示指针不能被修改

namespace zyh
{
	class string
	{
	public:
		string(const char* s = "")
		{
			_size = strlen(s);
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, s);
		}
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = 0;
			_capacity = 0;
		}
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		typedef const char* const_iterator; //指向的内容不能修改,指针本身可以修改
		const_iterator begin() const 
		{
			return _str;
		}
		const_iterator end() const
		{
			return _str + _size;
		}

		const char* c_str() const  //不需要修改,因此只有一个const类型就可以,const对象可以调用,非const对象也可以调用
		{
			return _str;
		}
		char& operator[](size_t pos)//const对象调用时不能修改,非const对象调用时要能修改,因此需要两种类型
		{
			assert(pos <= _size);
			return _str[pos];
		}
		const char& operator[](size_t pos) const
		{
			assert(pos <= _size);
			return _str[pos];
		}

	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

增删查改

push_back

        void push_back(char ch)
		{
			if (_size == _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		};

reserve

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

append

		void append(char* s)
		{
			size_t len = strlen(s);//没有\0
			if ((_size + len) > _capacity)
			{
				reserve(_size + len + 1);
			}
			strcpy(_str + _size, s);
			_size += len;
		}

+=

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

insert

插入字符串时:扩容写错

strcpy会把\0也拷贝进去
如果不想把\0也拷进去,就用strncpy

		string& insert(size_t pos, const char* s)
		{
			assert(pos <= _size);
			size_t len = strlen(s);
			if (_size + len > _capacity)
			{
				//size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(_size+len);
			}
			size_t end = _size+1;
			while (end > pos)
			{
				_str[end + len-1] = _str[end-1];
				--end;
			}
			for (size_t i = 0; i < len; i++)
			{
				_str[pos + i] = s[i];
			}
			return *this;
		}

上述代码在拷贝上可以简化,其次在返回值上不太对

        void insert(size_t pos, const char* s)
		{
			assert(pos <= _size);
			size_t len = strlen(s);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			int end = _size;
			while (end >= (int)pos)
			{
				_str[end + len] = _str[end];
				end--;
			}
			strncpy(_str + pos, s, len);
		}
#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
using namespace zyh;

void test1()
{
	string s1("hello world");
	s1.insert(5, "zyh");
	std::cout << s1.c_str() << std::endl;
}

int main()
{
	test1();

	return 0;
}

在这里插入图片描述

erase

1、缺省值忘写

npos不属于某个对象,它是属于整个类,因此要写成静态的
可以认为是编译器的特殊处理,当为const的时候既算是也算是定义
在成员变量那里定义,而且这个用法只支持整型

const static size_t npos = -1;

在这里插入图片描述

普通静态成员变量,在类里面声明,在类外面定义,不能给缺省值,因为缺省值是初始化列表初始化时用的
2、使用strcpy、strncpy

3、不判断len!=npos
当len为npos时是不能加的,因为一加就溢出了
在这里插入图片描述

		void erase(size_t pos = 0, size_t len = npos)
		{
			assert(pos <= _size);
			size_t end = _size;
			if (pos + len >= _size || len == npos)
			{
				end = _size;
			}
			else
			{
				end = pos + len;
			}
			strcpy(_str + pos, _str + end);
		}

swap

1、类里面swap的写法

		void swap(string& y)
		{
			std::swap(_str, y._str);
		}

2、三种swap的意义
成员函数里面的swap
在这里插入图片描述
非成员函数的swap
在这里插入图片描述
公共的swap
在这里插入图片描述
在这里插入图片描述

第二种swap底层实现上还是s1.swap(s2)

find

		size_t find(const char* str, size_t pos = 0)
		{
			assert(pos <= _size);
			const char* ptr = strstr(_str + pos, str);
			if (ptr == nullptr)
				return npos;
			else
				return ptr - _str;
		}

使用了strstr

substr

从某个位置取len个字符
1、考虑空间大小

		string substr(size_t pos = 0, size_t len = npos)
		{
			assert(pos <= _size);
			size_t end = _size;
			if (pos + len < _size && len != npos)
			{
				end = pos + len;
			}
			string tmp;
			tmp.reserve(end - pos);
			for (size_t i = pos; i < end; i++)
			{
				tmp += _str[i];
			}
			return tmp;
		}

上述代码存在问题

2、传值返回 - 浅拷贝问题
当没写拷贝构造的时候,默认生成的是浅拷贝
在这里插入图片描述

拷贝构造

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

=

		string& operator=(const string& s)
		{
			if (this != &s)
			{
				char* tmp = new char[s._capacity + 1];
				strcpy(tmp, s._str);
				delete[] _str;
				_str = tmp;
				_capacity = s._capacity;
				_size = s._size;
			}
			return *this;
		}

流插入和流提取

流插入和流提取不用非要设计为友元,因为不需要访问私有成员,用范围for、迭代器什么的就可以

<<流插入

	std::ostream& operator<<(std::ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			out << ch;
		}
		return out;
	}

>>流提取

1、对于空格或换行不能提取

cin不能提取空格和换行
为了解决上面的问题可以用cin.get()
这个get是不管是什么都提取
在这里插入图片描述

	std::istream& operator>>(std::istream& in, string& s)
	{
		char ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			s +=  ch;
			ch = in.get();
		}
		return in;
	}

2、多次对同一个string进行cin,没有覆盖,而是在后面增加

解决方法:
写一个clear,每次cin的时候先clear一下

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

3、如果一次性输入内容太多,会一直扩容

    std::istream& operator>>(std::istream& in, string& s)
	{
		s.clear();
		char buff[128];
		char ch = in.get();
		int i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 127)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get();
		}
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}

在这里插入图片描述

clear

为什么要置\0,如果不置\0会出现下面问题

cout是按_size一个字符一个字符的走
c_str则是看\0
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

		void clear()
		{
			_size = 0;
			_str[0] = '\0';
		}

深浅拷贝

传统写法

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

现代写法

问题:如果初始化时指向是空那没有问题,如果是随机值就会出问题
解决方法:
初始化列表处理一下或给缺省值

		string(const string& s)
		{
			string tmp(s._str);
			swap(tmp);
		}

赋值

传统写法和现代写法没什么区别,只是更简洁一点

传统写法

		string& operator=(const string& s)
		{
			if (this != &s)
			{
				char* tmp = new char[s._capacity + 1];
				strcpy(tmp, s._str);
				delete[] _str;
				_str = tmp;
				_capacity = s._capacity;
				_size = s._size;
			}
			return *this;
		}

现代写法

没必要检查是否是自己赋值给自己了,因为已经早就拷贝构造了,在传参的时候

		string& operator=(string s)
		{
			swap(s);
			return *this;
		}

在这里插入图片描述
在这里插入图片描述

string的声明和定义分离

1、不认识官方库的内容
(1)没包头文件
(2)命名空间的问题
2、缺省参数
只能在声明给,不能声明定义同时给
3、重定义问题
所以的重定义都是由于在多个cpp里面定义导致的

  • 19
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值