Underlying principle of C++ string

Keywords: C++ data structure linked list STL

1, Deep and shallow copy
Shallow copy:

When implementing a string, if you do not copy the string first, a copy constructor will be automatically generated, but it is only a shallow copy. Two string objects point to the same address. When two objects call the destructor, the destructor called by the previous object has released the internal slave of this address, and the latter will repeatedly release the block space, resulting in an error.


The breakpoint will be triggered and an error will be reported

class string
{
public:
/*string()
:_str(new char[1])
{*_str = '\0';}
*/
//string(const char* str = "String (const char * STR = "\ 0"") error demonstration
//string(const char* str = nullptr) error demonstration
string(const char* str = "")
{
// When constructing a string class object, if the nullptr pointer is passed, it is considered that the program is illegal. Assert here
if(nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
~string()
{
if(_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
// test
void Teststring()
{
string s1("hello bit!!!");
string s2(s1);
}


Note: the above string class does not explicitly define its copy constructor and assignment operator overload. At this time, the compiler will synthesize the default. When constructing s2 with s1, the compiler will call the default copy constructor. The final problem is that s1 and s2 share the same memory space. When released, the same space is released many times, resulting in program crash. This copy method is called shallow copy.

If a resource is managed in an object, it will eventually lead to multiple objects sharing the same resource. When an object is destroyed, the resource will be released. At this time, other objects do not know that the resource has been released and think it is still valid. Therefore, when the resource input operation continues, an access violation will occur. To solve the problem of shallow copy, deep copy is introduced into C + +.

Deep copy

If a class involves resource management, its copy constructor, assignment operator overload and destructor must be explicitly given. Generally, it is provided in the form of deep copy.

2, string iterator principle
string iterators are actually like this;

	    typedef char* Iterator;
		typedef const  char* const_Iterator;
		typedef char* reserve_Iterator;
		

In fact, the pointer traverses the string by moving the pointer forward and backward through the interfaces of begin(),end(),rend(),rbegin(),cend(),dbegin().

		typedef char* Iterator;
		Iterator begin() {
			return str;
		}
		Iterator end() {
			return str + _size;
		}
		string::iterator it=s.begin();
		while(it!=s.end()){
		cout<<*it<<endl;
		}

Here, typedef char* Iterator has been used to explain. The interface begin() actually returns the first pointer, while end() returns the last pointer of the string. Move the pointer by + +.
3, Traditional writing of string
1. Construction and Implementation
First, solve the construction of string

string_str(const char* _str="")
			:_size(strlen(_str)),
			str(new char[strlen(_str) + 1]),
			_capasity(strlen(_str))
		{
			strcpy(str, _str);
		}
		string_str(string_str& st1)
			:str(new char[strlen(st1.str) + 1])
		{

			strcpy(this->str, st1.str);

		}

		~string_str() {
			delete[] str;
			str = nullptr;

		}

When implementing the constructor, use deep copy, because the shallow copy string is a constant in the constant area and cannot be modified. Use deep copy to open up space in the heap area, so that the string can be modified.
Then there is the parameterless construction. In the string source code, the parameterless construction initializes the capacity to 15, and I initialize it to 0 in the implementation.

	string_str(const string_str& st)
			:str(nullptr)
	{
			string_str tem(st.str);
			swap(this->str, tem.str);

		}

The copy structure uses deep copy to create a space with the same size as this and copy the contents of str to this.
2. Other interfaces
operator=

	/*	string_str& operator=(const string_str& st) {
			if (this != &st) {
				char* s = new char[strlen(st.str) + 1];
				delete[] this->str;
				this->str = s;
				strcpy(this->str, st.str);

			}
			return *this;


		}*/

The idea is basically the same as the copy structure. Deep copy is adopted to create a space with the same size as this and copy the contents of str to this.
reserve()

 void reserve(size_t num) {
			 if (num >= _capasity) {
				char* str1 = new char[num + 1]; 
			
				 strcpy( str1,this->str);
				
				 delete[] str; 
				 this->str = str1;
					_capasity = num;
					
				 
			 }
		 }

If num is smaller than capacity, it will not be processed. If it is larger than capacity, it will be expanded to open up a memory of num size, and then copy the contents of this to the newly opened memory.
push_back() and append()

		 void push_back(char ch) {
			 if (_size >= _capasity) {
				 size_t num = _capasity == 0 ? 4 : 2 * _capasity;
				 this->reserve(num);

			 }
			 str[_size] = ch;
			 _size++;
			 str[_size] = '\0';
			 //\End of 0 flag string
		 
		 }
		 void append(const char* ch) {
			 size_t len = strlen(ch);
			 if (_size + len > _capasity) {
				 this->reserve(_size + len);
			 }
			 strcpy(this->str+_size,ch);
			 _size += len;
			
		 }

resize():

		void resize(size_t num,char ch='\0') {
			 if (num <= this->_size) {
				 this->str[num] = '\0';
				 this->_size = num;
			 }
			 else {
				 
				 if (num >_capasity) {
					 reserve(num);
				 }
			 for (int i = _size; i < num; i++) {
				 str[i] = ch;
			}
			 _size = num;
			 str[num] = '\0';
			 }
		 }
		 size_t size() {
		 
			 return _size;
		 }
		 size_t capacity() {
			 return _capasity;
		 }

There are three situations:
1.num is smaller than size(). Just add \ 0 to str[size].
2.num is larger than size and smaller than capacpty. Copy size to num in str as ch
3.num is larger than capacpty. First expand the capacity, and then copy the size to num as ch.

Posted by magicmoose on Wed, 17 Nov 2021 08:28:58 -0800