C++ string类的模拟实现

news2025/1/26 15:26:33

模拟实现string类不是为了造一个更好的轮子,而是更加理解string类,从而来掌握string类的使用

string类的接口设计繁多,故而不会全部涵盖到,但是核心的会模拟实现 

库中string类是封装在std的命名空间中的,所以在模拟实现中我们也可以用命名空间来封装

string类的实现可以在.h头文件中,.cpp文件中再来实际操作string类对象

完整的代码实现:

namespace djx
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		const_iterator begin()const
		{
			return _str;
		}

		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(const string& s)
		{
			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}*/

		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

		//现代写法
		string(const string& s)
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{
			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;
				_size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}*/

		//现代写法1
		/*string& operator=(const string& s)
		{
			if (this != &s)
			{
				string tmp(s);
				swap(tmp);
			}
			return *this;
		}*/

		//现代写法2
		string& operator=(string s)
		{
			swap(s);
			return *this;
		}


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

		const char* c_str()const
		{
			return _str;
		}

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

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

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

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

		void append(const char* s)
		{
			size_t len = strlen(s);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			strcpy(_str+_size, s);
			_size += len;
		}

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

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

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

		void insert(size_t pos, char ch)
		{
			assert(pos <= _size);
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}

			size_t end = _size+1;
			while (end >pos)
			{
				_str[end] = _str[end-1];
				end--;
			}
			_str[pos] = ch;
			_size++;
		}

		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);
			_size += len;
		}

		void erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				size_t begin = pos + len;
				while (begin <= _size)
				{
					_str[begin - len] = _str[begin];
					begin++;
				}
				_size -= len;
			}
		}

		bool operator<(const string& s)const
		{
			return strcmp(_str, s._str) < 0;
		}

		bool operator==(const string& s)const
		{
			return strcmp(_str, s._str) == 0;
		}

		bool operator<=(const string& s)const
		{
			return *this < s || *this == s;
		}

		bool operator>(const string& s)const
		{
			return !(*this <= s);
		}

		bool operator>=(const string& s)const
		{
			return !(*this < s);
		}

		bool operator!=(const string& s)const
		{
			return !(*this == s);
		}

		void resize(size_t n,char ch='\0')
		{
			if (n <= _size)
			{
				_str[n] = '\0';
				_size = n;
			}
			else
			{
				reserve(n);
				while (_size < n)
				{
					_str[_size] = ch;
					_size++;
				}
				_str[_size] = '\0';
			}
		}

		size_t find(char ch, size_t pos = 0)
		{
			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == ch)
				{
					return i;
				}
			}
			return npos;//没有找到
		}

		size_t find(const char* s, size_t pos=0)
		{
			const char* p = strstr(_str+pos, s);
			if (p)
			{
				return p - _str;
			}
			else
			{
				return npos;
			}
		}

		string substr(size_t pos, size_t len = npos)
		{
			string s;
			size_t end = pos + len;
			if (len == npos || pos + len >= _size)
			{
				len = _size - pos;
				end = _size;
			}

			s.reserve(len);
			for (size_t i = pos; i < end; i++)
			{
				s += _str[i];
			}

			return s;
		}

		size_t size()const
		{
			return _size;
		}

		size_t capacity()const
		{
			return _capacity;
		}

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

	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	public:
		const static size_t npos;
		//const static size_t npos=-1;//特例
		//const static double npos = 1.1;  // 不支持
	};
	const size_t string::npos = -1;

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

	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char buff[129];
		size_t i = 0;

		char ch;
		ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 128)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get();
		}

		if (i != 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
}

 

构造函数和析构函数:

namespace djx
{
	class string
	{
	public:
		string(const char* str = "")//构造函数
			:_size(strlen(str))//在对象中的成员都要走初始化列表(成员们定义的地方)
			,_capacity(_size)
		{
			_str = new char[_capacity + 1];//多开一个空间给'\0'
			strcpy(_str, str);
		}

		~string()//析构函数
		{
			delete[] _str;//释放空间+调用对象的析构函数(用于清理申请的资源)
			_str = nullptr;
			_size = _capacity = 0;
		}

        const char* c_str()const//返回c格式的字符串,加const用以修饰this指针,让const对象可以调用,非const对象也是可以调用的
		{
			return _str;
		}

	private:
		char* _str;//指向存储字符串的空间
		size_t _size;//有效字符的个数
		size_t _capacity;//存储有效字符的容量
	};
}

测试:

string类的对象可以重载流插入,流提取运算符,但现在我们还没有实现,又想查看string类中字符串的内容,可以用c_str 得到_str

void test1()
{
	djx::string s("hello");
	cout << s.c_str() << endl;

	djx::string s2;
	cout << s2.c_str() << endl;
}

构造函数:

1 库中string实现:对于无参的string,初始化为空字符串,对于有参的string,则初始化为参数内容

所以可以给构造函数缺省参数,缺省值是"",注意不能是" " 因为空格也是有效字符,也没必要是"\0",因为常量字符串自动会带有一个'\0'

2 _size 和_capacity是不算'\0'的大小的,它只是一个标识字符,因为c语言需要'\0'作为字符串的结束标志,c++不能抛弃c,所以'\0'被保留下来了

   实际在开空间时也是需要为'\0'预留一个空间的

string的三种遍历方式:

operator[]重载:

#include<assert.h>
namespace djx
{
	class string
	{
	public:
		string(const char* str = "")
			:_size(strlen(str))
			,_capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

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

		const char* c_str()const
		{
			return _str;
		}

		char& operator[](size_t pos)//非const对象调用,返回引用,可读可写
		{
			assert(pos < _size);
			return _str[pos];
		}

		const char& operator[](size_t pos)const//const对象调用,只读不可写
		{
			assert(pos < _size);
			return _str[pos];
		}

		size_t size()const//const对象可以调用,非const对象也可以调用
		{
			return _size;
		}

		size_t capacity()const
		{
			return _capacity;
		}

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

测试:

void test2()
{
	djx::string s("hello");
	for (size_t i = 0; i < s.size(); i++)
	{
		cout << s[i];
	}
	cout << endl;
}

 迭代器:

string类的迭代器可以看作是指针,因为它完美契合指针的行为

namespace djx
{
	class string
	{
	public:
		typedef char* iterator;//非const对象的迭代器
		typedef const char* const_iterator;//const对象的迭代器

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		const_iterator begin()const
		{
			return _str;
		}

		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()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}

		const char* c_str()const
		{
			return _str;
		}

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

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

		size_t size()const
		{
			return _size;
		}

		size_t capacity()const
		{
			return _capacity;
		}

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

测试:

void test3()
{
	djx::string s("hello");
	cout << s.c_str() <<endl;

	djx::string::iterator it = s.begin();
	while (it != s.end())
	{
		(*it)++;//写
		cout << *it;//读
		it++;
	}
	cout << endl;
}

 范围for:

范围for的底层原理是迭代器,所以有了迭代器就可以使用范围for

测试:

void test4()
{
	djx::string s("hello");
	cout << s.c_str() << endl;
	for (auto &e : s)//不加引用就是只读
	{
		e++;//写
		cout << e;//读
	}
	cout << endl;
}

插入和删除操作:

push_back:

 尾插一个字符,插入之前需要检查是否需要扩容,扩容扩至原来的2倍

注意:_size==_capacity 可能是第一次插入,双方都是0,那么2*_capacity就是0,所以如果是第一次插入的扩容,可以扩4个空间

扩容可以使用reserve:

1 开n个空间,实际上reserve要开n+1个空间,因为要存'\0'

2 拷贝数据到新空间

3 释放旧空间

4 指向新空间


append:

 插入之前要检查是否需要扩容:原有效字符个数+要插入的有效字符个数>_capacity则扩容

operator+=:

目前阶段的完整代码:

#include<assert.h>
namespace djx
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		const_iterator begin()const
		{
			return _str;
		}

		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()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}

		const char* c_str()const
		{
			return _str;
		}

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

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

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

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

		void append(const char* s)
		{
			size_t len = strlen(s);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			strcpy(_str+_size, s);
			_size += len;
		}

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

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

		size_t size()const
		{
			return _size;
		}

		size_t capacity()const
		{
			return _capacity;
		}

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

测试:

 

void test5()
{
	djx::string s("hello");
	cout << s.c_str() << endl;

	s.push_back(' ');
	s.append("world");
	cout << s.c_str() << endl;

	s += '#';
	s += "!!!!!!!!!";
	cout << s.c_str() << endl;

	djx::string s2;
	s2 += '#';
	s2 += "!!!!!!!!!!";
	cout << s2.c_str() << endl;
}

insert:

 插入一个字符,版本1:

在pos位置插入一个字符,就需要从'\0'开始直到pos位置,将这些位置上的数据全部向后挪动一位

不要忘记把'\0'一并移走 

注意:如果是头插,pos==0,且end是size_t类型,那么循环的结束条件是end<pos

即end<0,但由于end是size_t类型是不会<0的,让end写成int类型,那么end可以达到-1,为避开隐式类型转换,将end由int提升为size_t,所以pos也要强制为int类型,这样当pos为0时,end可以达到-1,-1<0结束循环 

 版本2:

 版本1利用强转来解决循环条件的结束问题,版本2不强转:

将end作为最后一个要挪动的数据的最终位置,end==pos+1的位置时,pos位置上的值已经挪到pos+1上了,当end==pos位置时,结束循环

插入常量字符串:

 可以尾插,所以pos可以是_size

1 检查原有效字符个数+要插入的有效字符格式是否>_capacity,大于则扩容

2 从'\0'位置开始,一直到pos位置上的数据,将它们全部向后挪动len步

3 将常量字符串拷贝到_str+pos的位置,只要拷贝len个,不要使用strcpy全部拷贝,因为会拷贝到'\0'

erase:

有两种情况:

一:从pos位置开始,有多少删多少

1 当没有给len传参时,len使用缺省值npos,(size_t类型的-1,很大的数字,但是一个字符串不会有那么大,所以npos通常可以理解为有多少要多少),即从pos位置开始全部删除

2  传参给len,但若pos+len>=_size (pos+len是要删除的最后一个数据的下一个位置),也是从pos位置开始全部删除

方法:在pos位置给'\0'

二:从pos位置开始,删除len个字符 

pos+len是合法的,那么从pos+len位置开始一直到'\0',要将它们全部向前移动len步,覆盖效果

不要忘记移动'\0'

 len给一个缺省值npos(const修饰的静态变量,值为-1)

 静态成员变量不在对象中,不走初始化列表,在类外定义,给初始值-1

但是 const修饰的静态整型成员变量是一个特例,可以在声明的地方给缺省值

其实在常规情况下,成员变量声明的地方给缺省值,就代表它们在走初始化列表的时候可以被初始化

流插入和流提取重载:

流插入:

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

会有cout<<s<<s2的情况,所以有ostream类型的返回值 ,out出了作用域还在所以可以用传引用返回提高效率

流提取:

        void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}
    istream& operator>>(istream& in, string& s)
	{
		s.clear();//在向string类的对象输入内容时,要情况原有的所有数据
		char buff[129];
		size_t i = 0;

		char ch;
		ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 128)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;//从头再来
			}
			ch = in.get();
		}

		if (i != 0)//可能字符串的长度没有达到128个
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}

会有cin>>s>>s2的情况,所以有istream类型的返回值

在输入的字符串很长的情况下,若是提取一个字符便+=到string类的对象中,难免会有多次扩容,

若以为了提高效率,可以开一个能存储129个字符的buff数组,提取的字符先存储到buff数组中

当有了128个字符时,加入'\0',再将buff数组中的所有字符以字符串的形式一并+=到string类的对象中 

buff数组就像一个蓄水池,水满了就全部拿走,然后再接着蓄水,满了再全部拿走

注意:cin和scanf一样,不能提取到空格或者'\n',scanf中用getchar可以提取任意字符,那在cin中,有get可以提取任意字符

测试:

void test6()
{
	djx::string s("hello world");
	cout << s.c_str() << endl;//没有重载流插入运算符之前,打印string类对象的内容

	s.insert(5, '%');
	cout << s.c_str() << endl;

	s.insert(s.size(), '%');
	cout << s.c_str() << endl;

	s.insert(0, '%');
	cout << s.c_str() << endl;

	djx::string s2("hello world");
	s2.insert(5, "abc");
	cout << s2 << endl;//重载流插入运算符之后,打印string类对象的内容

	s2.insert(0, "xxx");
	cout << s2 << endl;

	s2.erase(0, 3);
	cout << s2 << endl;

	s2.erase(5, 100);
	cout << s2 << endl;

	s2.erase(2);
	cout << s2 << endl;

}

void test7()
{
	djx::string s("hello world");
	cout << s << endl;
	cin >> s;
	cout << s << endl;
}

 

 

 目前为止的完整代码:

#include<assert.h>
namespace djx
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		const_iterator begin()const
		{
			return _str;
		}

		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()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}

		const char* c_str()const
		{
			return _str;
		}

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

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

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

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

		void append(const char* s)
		{
			size_t len = strlen(s);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			strcpy(_str+_size, s);
			_size += len;
		}

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

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

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

		void insert(size_t pos, char ch)
		{
			assert(pos <= _size);
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}

			size_t end = _size+1;
			while (end >pos)
			{
				_str[end] = _str[end-1];
				end--;
			}
			_str[pos] = ch;
			_size++;
		}

		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);
			_size += len;
		}

		void erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				size_t begin = pos + len;
				while (begin <= _size)
				{
					_str[begin - len] = _str[begin];
					begin++;
				}
				_size -= len;
			}
		}

		size_t size()const
		{
			return _size;
		}

		size_t capacity()const
		{
			return _capacity;
		}

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

	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	public:
		const static size_t npos;
		//const static size_t npos=-1;//特例
		//const static double npos = 1.1;  // 不支持
	};
	const size_t string::npos = -1;

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

	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char buff[129];
		size_t i = 0;

		char ch;
		ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 128)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get();
		}

		if (i != 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
}

关系运算符重载:

        bool operator<(const string& s)const
		{
			return strcmp(_str, s._str) < 0;
		}

		bool operator==(const string& s)const
		{
			return strcmp(_str, s._str) == 0;
		}

		bool operator<=(const string& s)const
		{
			return *this < s || *this == s;
		}

		bool operator>(const string& s)const
		{
			return !(*this <= s);
		}

		bool operator>=(const string& s)const
		{
			return !(*this < s);
		}

		bool operator!=(const string& s)const
		{
			return !(*this == s);
		}

resize:

        void resize(size_t n,char ch='\0')
		{
			if (n <= _size)//删除效果
			{
				_str[n] = '\0';
				_size = n;
			}
			else//插入效果
			{
				reserve(n);
				while (_size < n)
				{
					_str[_size] = ch;
					_size++;
				}
				_str[_size] = '\0';
			}
		}

ch的缺省值给'\0',若是没有传参给ch,那么就用'\0'填充 

分三种情况:

n<=_size:删除效果,保留前n个有效字符

_size<n<_capacity : 无需扩容,直接插入

n>_capacity :先扩容,再插入

测试:

void test8()
{
	djx::string s("hello world");
	cout << s<< endl;

	s.resize(5);
	cout << s << endl;

	s.resize(25, 'x');
	cout << s << endl;
}

 find:

查找一个字符:

        size_t find(char ch, size_t pos = 0)
		{
			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == ch)
				{
					return i;
				}
			}
			return npos;//没有找到
		}

pos为0,若没有传参给pos则默认从头开始找

查找字符串:

	    size_t find(const char* s, size_t pos=0)
		{
			const char* p = strstr(_str+pos, s);//指向匹配字符串的首字符
			if (p)
			{
				return p - _str;
			}
			else
			{
				return npos;//找不到
			}
		}

测试:

void test()
{
	djx::string s("hello world");
	size_t i = s.find("world");
	cout << i << endl;
}

 

 

substr:

        string substr(size_t pos, size_t len = npos)
		{
			string s;
			size_t end = pos + len;
			if (len == npos || pos + len >= _size)//从pos位置开始有多少取多少
			{
				len = _size - pos;
				end = _size;
			}

			s.reserve(len);//提前开空间,避免多次扩容
			for (size_t i = pos; i < end; i++)
			{
				s += _str[i];
			}

			return s;//传值返回
		}

拷贝构造:

传统写法和现代写法在效率上区别不大,只是代码简洁性的区分

传统写法:

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

自己开和s一样大的空间,拷贝数据

现代写法:

        void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

		//现代写法
		string(const string& s)
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{
			string tmp(s._str);//调用构造函数
			swap(tmp);
		}
		

 

 

让构造函数去生成tmp,再与tmp交换

当tmp销毁时,调用tmp的析构函数会把this指向的对象,它申请的资源给释放掉

注意:this指向的对象中的成员变量都要走初始化列表(是它们定义的地方)

那么如果不给this指向对象的成员变量初始化,那么this指向的对象中的_str指向的是一块随机的空间,是野指针,不能随意释放这块不属于自己的空间

所以需要在初始胡列表的地方给this指向对象中的成员变量初始化

赋值重载:

传统写法:

        //传统写法
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				char* tmp = new char[s._capacity + 1];
				strcpy(tmp, s._str);

				delete[] _str;//释放旧空间
				_str = tmp;//指向新空间
				_size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}

1 tmp指向一块与s一模一样的空间

2 释放_str指向的空间

3 _str指向tmp指向的空间

现代写法:

版本1:

        //现代写法1
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				string tmp(s);//调用拷贝构造生成tmp
				swap(tmp);
			}
			return *this;
		}

版本2:

 


		//现代写法2
		string& operator=(string s)//拷贝构造生成s
		{
			swap(s);
			return *this;
		}

 s销毁,会释放原本由*this对象申请的空间

测试:

void test9()
{
	djx::string s("test.cpp.tar.zip");
	size_t i = s.find('.');
	cout << i << endl;
	djx::string sub = s.substr(i);
	cout << sub << endl;

	djx::string s2("https://legacy.cplusplus.com/reference/string/string/rfind/");
	//分割 协议、域名、资源名
	djx::string sub1;
	djx::string sub2;
	djx::string sub3;

	size_t i1 = s2.find(':');
	if (i1 != djx::string::npos)
	{
		sub1 = s2.substr(0, i1);
	}
	else
	{
		cout << "找不到i1" << endl;
	}

	size_t i2 = s2.find('/', i1 + 3);
	if (i2 != djx::string::npos)
	{
		sub2 = s2.substr(i1+3, i2-(i1+3));
	}
	else
	{
		cout << "找不到i2" << endl;
	}

	sub3 = s2.substr(i2 + 1);

	cout << sub1 << endl;
	cout << sub2 << endl;
	cout << sub3 << endl;
}

 注意1:

 

 substr是传值返回,s对象出了作用域之后就销毁了,会生成一个临时对象,由s拷贝构造生成

若是我们没有写拷贝构造函数,编译器默认生成的拷贝构造函数对于对象中内置类型的成员只会完成浅拷贝(值拷贝),即临时对象和s对象管理同一块空间,但是s出了作用域之后会调用析构函数,这块空间会被释放掉

 

substr返回的临时对象再拷贝构造给sub对象,没写拷贝构造函数,编译器生成的拷贝构造依然是值拷贝,那么sub对象和临时对象管理同一块空间,临时对象任务完成后,销毁,再次对已经释放的空间进行释放,导致程序崩溃

这里还涉及编译器优化的问题:

substr拷贝构造生成临时对象,再由临时对象拷贝构造生成sub,连续的拷贝构造动作,编译器直接一步优化为一个拷贝构造:让s在销毁前先拷贝构造生成sub,即使是优化了,也因为浅拷贝的问题导致程序崩溃-> sub 和s管理同一块资源空间,s销毁,释放一次这块空间,等sub销毁时,再一次释放这块空间

所以拷贝构造必须由我们来写,完成深拷贝,让每个对象有自己独立的资源空间 

注意2 :

 

解决拷贝构造问题后,由原来的浅拷贝变为了深拷贝,那么substr返回的临时对象就有和s一模一样的独立资源空间

但是若是我们没有写赋值重载函数,那么编译器默认生成的赋值重载函数对于对象中的内置类型的成员只会浅拷贝,即sub1和临时对象管理同一块空间,且sub1原来管理的资源空间丢失,导致内存泄漏,且临时对象销毁,对它管理的资源空间释放一次,当sub1销毁,又对这块空间释放一次,程序崩溃

 

 所以我们也要显示写赋值重载函数,完成深拷贝,让每个对象有自己独立的,与赋值对象一模一样的资源空间,而不是和其他对象共享同一块资源空间的管理

完结撒花~

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

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

相关文章

ImageKit10 VCL Crack

ImageKit10 VCL Crack ImageKit10 VCL是一个允许您快速轻松地将图像处理功能添加到应用程序中的组件。使用ImageKit10 VCL&#xff0c;您可以编写从TWAIN扫描仪和数码相机检索图像的应用程序;加载和保存图像文件&#xff0c;并将图像从一种格式转换为另一种格式;编辑图像、在图…

MySQL的Json类型字段IN查询分组和优化方法

前言 MySQL从5.7的版本开始支持Json后&#xff0c;我时常在设计表格时习惯性地添加一个Json类型字段&#xff0c;用做列的冗余。毕竟Json的非结构性&#xff0c;存储数据更灵活&#xff0c;比如接口请求记录用于存储请求参数&#xff0c;因为每个接口入参不一致&#xff0c;也…

python的交互式库Qgrid

目录 Qgrid介绍Qgrid使用Qgrid使用过程中遇到的问题解决方案 Qgrid介绍 在Jupyter notebook中直接读取DataFrame数据&#xff0c;只显示为静态表格的形式&#xff0c;没有类似于excel的筛选等交互式功能。Qgrid作为 Jupyter notebook 组件&#xff0c;可以为我们的 DataFrame …

三本书与三场发布会,和鲸社区重新定义编程类书籍从阅读到实践新体验

当 AI 开发者社区配备 AI 基础设施开发平台工具时&#xff0c;它还能做什么&#xff1f; 答案是&#xff1a;过去半年&#xff0c;和鲸社区凭借在气象、医学、社科等垂直领域的长期积累以及多方伙伴的支持&#xff0c;联合举办了三场新书发布会——从 Python 到 R 语言 、从气…

程序员与ChatGPT的交织:探索人工智能和软件开发的新篇章

目录 前言创作者程序员会被替代吗程序员如何更好的使用chatgpt 前言 在技术持续进步的当今世界&#xff0c;程序员与人工智能&#xff08;AI&#xff09;之间的关系越来越紧密。特别是对于一些创新性的技术如OpenAI旗下的ChatGPT&#xff0c;这种联系就更为明显。程序员与Chat…

2023/8/16 华为云OCR识别驾驶证、行驶证

目录 一、 注册华为云账号开通识别驾驶证、行驶证服务 二、编写配置文件 2.1、配置秘钥 2.2、 编写配置工具类 三、接口测试 3.1、测试接口 3.2、结果 四、实际工作中遇到的问题 4.1、前端传值问题 4.2、后端获取数据问题 4.3、使用openfeign调用接口报错 4.3、前端显示问题…

python bytes基本用法

目录 1 第一个字符变大写&#xff0c;其余字符变小写 capitalize() 2 生成指定长度内容&#xff0c;然后把指定的bytes放到中间 center() 3 计数 count() 4 解码 decode() 5 是否以指定的内容结尾 endswith() 6 将制表符调整到指定大小 expandtabs() 7 寻找指…

ref拿到组件的实例对象或者原生html标签

在组件中&#xff0c;或者html标签中写ref属性&#xff0c;就是在注册引用 可以通过ref拿到组件的实例对象 也可以通过ref拿到原生的html标签

Linux系统安装及使用HHDBCS

1 安装 1.1 下载HHDBCS 使用浏览器进入官方社区&#xff08;恒辉产品社区&#xff09;&#xff0c;选择HHDBCS子社区&#xff0c;首页点击下载&#xff0c;进入下载页面&#xff1b; 选择官网下载/云盘下载皆可。 在弹出框中选择如图所示选项&#xff0c;点击下载&#xff…

带着设计思维画版图——第一次和第二次

版图设计目标&#xff1a; 面积小&#xff0c;性能好&#xff08;少恶化&#xff09;&#xff0c;成本低 设计规则规定了同层与不同层之间的最小距离&#xff0c;因此限制了最小面积 模拟版图设计流程 第一步&#xff1a;设计原理图输入 常用快捷键如下&#xff1a; 介…

YOLO算法封装进入ros系统,识别结果供其他节点订阅

一,前期工作空间搭建 新建工作空间,第一级名称可以换,第二级src最好别换,这是ros系统的固定格式 mkdir -p workspace_yolo/src切换到工作空间 workspace_yolo,进行编译构建项目 cd workspace_yolo/catkin_make输出如下所示: 添加环境变量 cd devel/ 获取到devel文件路径…

模型预测笔记(一):数据清洗分析及可视化、模型搭建、模型训练和预测代码一体化和对应结果展示(可作为baseline)

模型预测 一、导入关键包二、如何载入、分析和保存文件三、修改缺失值3.1 众数3.2 平均值3.3 中位数3.4 0填充 四、修改异常值4.1 删除4.2 替换 五、数据绘图分析5.1 饼状图5.1.1 绘制某一特征的数值情况&#xff08;二分类&#xff09; 5.2 柱状图5.2.1 单特征与目标特征之间的…

花生十三 判断推理(三)分析类、推出类

分析类 题型 真假分析 定义&#xff1a;孰真孰假的真假话分析&#xff0c;命题真假无法确定&#xff0c;无法利用推出关系解题 解题思路 矛盾法&#xff08;三种矛盾&#xff09;&#xff1a;A和非A&#xff0c;“A或B” 与“非A且非B” 技巧&#xff1a;一“找”矛盾&am…

在ARM服务器上一键安装Proxmox VE(以在Oracle Cloud VPS上为例)(甲骨文)

前言 如题&#xff0c;具体用到的说明文档如下 virt.spiritlhl.net 具体流程 首先是按照说明&#xff0c;先得看看自己的服务器符不符合安装 Proxmox VE的条件 https://virt.spiritlhl.net/guide/pve_precheck.html#%E5%90%84%E7%A7%8D%E8%A6%81%E6%B1%82 有提到硬件和软…

C# 读取pcd、ply点云文件数据

最近研究了下用pcl读取点云数据&#xff0c;又做了个C#的dll&#xff0c;方便读取&#xff0c;同样这个dll基于pcl 最新版本1.13.1版本开发。 上次做的需要先得到点云长度&#xff0c;再获取数据。这次这个定义了一个PointCloudXYZ类来存数据。将下面的dll拷贝到可执行目录下&a…

边缘网络的作用及管理工具

自从引入软件即服务 &#xff08;SaaS&#xff09; 以来&#xff0c;它一直引领着全球按需软件部署创新的竞赛&#xff0c;它提供的灵活性以及其云计算架构带来的易于集成使其成为交付业务应用程序的标准。 在 SaaS 模型中&#xff0c;最佳用户体验的三重奏涉及无缝设置、低延…

20230818 数据库自整理部分

并发事务 脏读 一个事务读取到另一事务还没有提交的数据 事务B读取了事务A还没有提交的数据 不可重复读 一个事务先后读取同一条记录&#xff0c;但是两次读取的数据不同&#xff0c;称之为不可重复读 查询出来的数据不一样 1步骤b还没有提交 3步骤b已经提交 幻读 一个…

利用dayj转换查询时间获取当前周月年最后一天

利用dayj转换查询时间 queryForm 查询参数对象 switch 区分选择时间类型 日 周 月 年 计算结束时间 dayjs(element).endOf("week").format("YYYY-MM-DD") 当前周结束时间 日期时间查询框配置参数格式 {label: "",width: 220,key: "…

中期国际:MT4挂单和止损设置教程:善用限价和止损单来管理风险

在外汇交易中&#xff0c;合理设置挂单和止损是保护资金和管理风险的重要手段。MT4平台提供了便捷的挂单和止损功能&#xff0c;帮助交易者更好地控制交易风险。本文将为您介绍如何善用限价和止损单来管理风险&#xff0c;以及在MT4平台上的操作步骤。 一、设置限价挂单 限价挂…

ZooKeeper单机服务器启动

ZooKeeper服务器的启动&#xff0c;大体可以分为以下五个主要步骤&#xff1a;配置文件解析、初始化数据管理器、初始化网络I/O管理器、数据恢复和对外服务。下图所示是单机版ZooKeeper服务器的启动流程图。 预启动 预启动的步骤如下。 (1)统一由QuorumPeerMain作为启动类。 …