C++【string类用法详细介绍string类模拟实现解析】

news2024/11/24 0:12:29

文章目录

  • string 类用法介绍及模拟实现
  • 一、string介绍
  • 二、string类常用接口
    • 1. string类对象的常见构造接口
    • 2.string类对象的常见容量接口
    • 3.string类对象的常见修改接口
    • 4. string类对象的常见访问及遍历接口
    • 5.string其他接口
      • 1.不常用查找接口
      • 2.字符替换
      • 3.字符串拼接
      • 4.字符串排序
      • 5.字符串比较
    • 6.细看string中某个函数接口的用法
  • 三、string类模拟实现及函数解析
    • (1)迭代器开始起始位置返回
    • (2)构造和拷贝函数
    • (3)赋值重载和析构函数
    • (4)返回重载和打印打符串
    • (5)字符串字符个数和容量大小函数
    • (6)字符串比较重载函数
    • (7)字符串两类扩容函数
    • (8)字符串插入函数和删除函数
    • (9)字符串拼接和拼接重载函数
    • (10)字符串交换和查找函数及清理函数
    • (11)流插入和流提取的重载函数
  • 四、string类模拟实现代码
    • (1)simulate_string.h
    • (2)test.cpp
    • (3)运行结果
  • 五、深浅拷贝及其他说明

string 类用法介绍及模拟实现

一、string介绍

背景:在C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。这时候就要用到C++的string类。这样就不必担心内存是否足够、字符串长度等。
介绍:string 是 C++ 中常用的一个类,也是C++标准库的重要组成部分,主要用于字符串处理 。string类本不是STL的容器,但是它与STL容器有着很多相似的操作。而且作为一个类出现,他集成的操作函数基本能完成我们大多数情况下的需要。我们可以把它看成是C++的基本数据类型。
另外还要注意这里的头文件是<string>,不是<string.h>,它是C字符串头文件。

关键结论
string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类
1.string是表示字符串的字符串类
2.该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
3. string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator>string。
4. 不能操作多字节或者变长字符的序列。

二、string类常用接口

1. string类对象的常见构造接口

string str:生成空字符串

string s(str):生成字符串为str的复制品

string s(str,begin,strlen):将字符串str中从下标begin开始、长度为strlen的部分作为字符串初值

string s(str, len):以str前len个字符串作为字符串s的初值

string s(num ,c):生成num个c字符的字符串

string s(str, x):将字符串str中从下标x开始到字符串结束的位置作为字符串初值
string(const string&s) :拷贝构造函数

    string str1;       //生成空字符串
    string str2("asdf");  //生成"asdf"的复制品
    string str3("12345", 1, 4);//结果为"2345"
    string str4("012345", 5);  //结果为"01234"
    string str5(6, 'a');      //结果为"aaaaaa"
    string str6(str2, 2);     //结果为"df"
    string s3(s2); // 拷贝构造s3

2.string类对象的常见容量接口

size: 返回字符串有效字符长度
length :返回字符串有效字符长度
capacity:返回空间总大小
empty :检测字符串释放为空串,是返回true,否则返回false
clear:清空有效字符
reserve:为字符串预留空间,不改变有效元素个数,当reserve的参数小于
string的底层空间总大小时,reserve不会改变容量大小。
resize:将有效字符的个数该成n个,多出的空间用字符填充,不够进行扩容再初始化。删除数据,容量大小不会变。如果将元素个数增多,size会变,可能会改变底层容量的大小。
在扩容的时候vs下是从15开始的,先以二倍进行扩容一次,后面都是以1.5倍进行扩容

	string s("hello nza");
	cout << s.size() << endl;//9
	cout << s.length() << endl;//9
	cout << s.capacity() << endl;//15

	s.clear();	// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
	cout << s << endl;空
	cout << s.size() << endl;//0
	cout << s.capacity() << endl;//15


	s.resize(10, 'a');//将s中有效字符个数增加到10个,多出位置用'a'进行填充
	cout << s.size() << endl;//10
	cout << s.capacity() << endl;//15
	
	s.resize(16);// 将s中有效字符个数增加到16个,多出位置用缺省值'\0'进行填充,空间原来为15,不够进行扩容
	cout << s.size() << endl;//16
	cout << s.capacity() << endl;//已扩容
	cout << s << endl;

	
	s.resize(5);// 将s中有效字符个数缩小到5个
	cout << s.size() << endl;//5
	cout << s.capacity() << endl;//31
	cout << s << endl;//aaaaa

	string s("aaa");
	s.reserve(166);//175
	cout << s.size() << endl;//3
	cout << s.capacity() << endl;//175

	
	s.reserve(50);
	cout << s.size() << endl;//3
	cout << s.capacity() << endl;//175
}

3.string类对象的常见修改接口

push_back:在字符串后尾插字符c
append:在字符串后追加一个字符串
operator+= :在字符串后追加字符串str
c_str:返回C格式字符串
find: 在当前字符串的pos(默认0)索引位置开始,查找子串s,返回找到的位置索引.
rfind:从字符串pos(默认npos)位置开始往前找字符c,返回该字符在字符串中的位置
substr:在str中从pos位置开始,截取n个字符,然后将其返回
insert(pos,char):在指定的位置pos前插入字符char
iterator erase(iterator p);删除字符串中p所指的字符
iterator erase(iterator first, iterator last):删除字符串中迭代器区间[first,last)上所有字符
string& erase(size_t pos = 0, size_t len = npos):删除字符串中从索引位置pos开始的len个字符

string str;
	str.append("hello");  // 在str后追加一个字符"hello"
	str += 'n';           // 在str后追加一个字符'n'   
	str += "za";          // 在str后追加一个字符串"za"
	cout << str << endl;//hellonza
	cout << str.c_str() << endl;   // 以C语言的方式打印hellonza

	string f("string.cpp");
	size_t pos = f.rfind('.');6
	string sub1(f.substr(pos, 3));
	cout << sub1<< endl;.cp

	string url("http://www.nzanzanza.com/kd/kd/kd/");
	size_t start = url.find("://");//4
	start += 3;//变为7
	size_t finish = url.find('/', start);//24
	string address = url.substr(start, finish - start);
	cout << address << endl;//www.nzanzanza.com
	
	//插入一个字符
	 string s("aaaa");
     s.insert(1,"555");
     cout << s << endl;//a555aaa
     string s1("rrrr");
	 s1.insert(0, 1, 'z');
	 cout << s1;//zrrrr
	 string s1("abc");
     cout<<s1<<endl; // s1:abc
     s1.insert(s1.begin(),'1');// insert(pos,char):在指定的位置pos前插入字符char
     cout<<s1<<endl; // s1:1abc
     
    // 尾插一个字符
     string s1;
    s1.push_back('a');
    s1.push_back('b');
    s1.push_back('c');
    cout<<s1<<endl; // s1:abc
    
    //删除一个字符
    string s1 = "123456789";
    s1.erase(s1.begin()+1);              // 结果:13456789
    s1.erase(s1.begin()+1,s1.end()-2);   // 结果:189
    s1.erase(1,6);                       // 结果:189

4. string类对象的常见访问及遍历接口

operator[] :返回pos位置的字符,const string类对象调用
begin+ end:(正向迭代器)begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
rbegin + rend:(反向迭代器)begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
范围for :C++11支持更简洁的范围for的新遍历方式

	string s("hello nza");
	// 1. for+operator[]
	for (size_t i = 0; i < s.size(); ++i)
	{
		cout << s[i];
	}
	cout << endl;
	// 2.迭代器
	string::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it ;
		++it;
	}
	cout << endl;//hellonza
	string::reverse_iterator rit = s.rbegin();
	// C++11之后,直接使用auto定义迭代器,让编译器推到迭代器的类型
	//auto rit=s.rbegin();
	while (rit != s.rend())
	{
		cout << *rit ;
		++rit;
	}
	cout << endl;//aznolleh
	// 3.范围for
	for (auto ch : s)
		cout << ch;//hellonza

5.string其他接口

1.不常用查找接口

1.size_tfind_first_of (const char* s, size_t pos = 0) const:在当前字符串的pos索引位置开始,查找子串s的字符,返回找到的位置索引,-1表示查找不到字符
2.size_tfind_first_not_of (const char* s, size_t pos = 0) const:在当前字符串的pos索引位置开始,查找第一个不位于子串s的字符,返回找到的位置索引,-1表示查找不到字符
3.size_t find_last_of(const char* s, size_t pos = npos) const:当前字符串的pos索引位置开始,查找最后一个位于子串s的字符,返回找到的位置索引,-1表示查找不到字符
4.size_tfind_last_not_of (const char* s, size_t pos = npos) const:在当前字符串的pos索引位置开始,查找最后一个不位于子串s的字符,返回找到的位置索引,-1表示查找不到子串

    string s("ddd bbrb ccccccc bbrb ccc");
    cout << s.find_first_of("mmmmbr98") << endl; 
     // 结果是:4
    
    cout << s.find_first_not_of("hhh ddd ") << endl;
     // 结果是:4

    cout << s.find_last_of("13r98") << endl;  
       // 结果是:19

    cout << s.find_last_not_of("teac") << endl;  
       // 结果是:21

2.字符替换

  1. string& replace(size_t pos, size_t n, const char *s);将当前字符串从pos索引开始的n个字符,替换成字符串s
  2. string& replace(size_t pos, size_t n, size_t n1, char c); 将当前字符串从pos索引开始的n个字符,替换成n1个字符c
  3. string& replace(iterator i1, iterator i2, const char* s);将当前字符串[i1,i2)区间中的字符串替换为字符串s
    string s1("hello world?");
    cout<<s1.size()<<endl;                     // 结果:12
    s1.replace(s1.size()-2,2,3,'.');           // 结果:hello worl...
    s1.replace(6,5,"green");                    // 结果:hello green.
    // s1.begin(),s1.begin()+5 是左闭右开区间
    s1.replace(s1.begin(),s1.begin()+5,"red"); // 结果:red green.
    cout<<s1<<endl;

3.字符串拼接

append() & + 操作符:拼接字符串

    string s1("aaa");
    s1.append("bbb");
    cout<<"s1:"<<s1<<endl; // s1:aaabbb

    // 方法二:+ 操作符
    string s2 = "aaa";
    string s3 = "bbb";
    s2 += s3.c_str();
    cout<<s2<<endl; // s2:aaabbb

4.字符串排序

sort(s.begin(),s.end()):排序 (要加#include <algorithm>头文件)

    string s = "cdefba";
    sort(s.begin(),s.end());
    cout<<s<<endl;     // 结果:abcdef

5.字符串比较

  1. C ++字符串支持常见的比较操作符(>,>=,<,<=,==,!=),两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇’\0’为止。
	cout << (string("1") < string("4")) << endl;//打印为1
	
    string str1("666");
    string str2("222");
     bool ret;
    ret = str1 > str2;
    cout << ret << endl;  // 1

    str1 = "111";
    ret = str1 > str2;
    cout <<ret << endl;    // 0

    str1 = "111";
    ret = str1 <str2;
    cout <<ret<< endl;//1
    
	string s1("fds");
	cout << (s1 < "few") << endl;//1
  1. 第二种用成员函数compare()。支持多参数处理,用索引值和长度定位子串来进行比较。 他返回一个整数来表示比较结果:1表示大于 ,-1表示小于, 0表示相等。
    string A("dag");
	string B("cvb");
	string C("2111");
	string D("9850");
	cout<< A.compare(B) << endl; // dag和cvbv比较 打印为1

	cout  << A.compare(1, 1, B) << endl;// 拿A的第一个位置起一个字符和B比较 结果:-1

	cout << C.compare(0, 3, D, 0, 3) << endl;//C第0位置前三个字符211和D前三个字符985比较,结果为-1

6.细看string中某个函数接口的用法

可登录www.cplusplus.com查看

三、string类模拟实现及函数解析

(1)迭代器开始起始位置返回

        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;
		}

开始直接返回首元素地址,结束返回的是最后一个元素的下一个位置的地址,另外迭代器还有const类型的,与它构成函数重载。

(2)构造和拷贝函数

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

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

1、构造函数:在构造函数里面传常量,那么私有域里面的_str就要加const,就变成在常量区,这样_str就不允许修改,扩容也不扩不了,而这里还需要修改内容,不够还需要扩容,所以最好空间是new出来的,从堆上申请出来的。
2、现在初始化列表就初始化一个_size,因为初始化列表是按private里面的顺序进行初始化,私有域一修改,可能会出现问题,而且用多了strlen,会造成时间浪费,所以后面的开空间和_capacity初始化操作放在里面。capacity+1存的是有效字符。
3、里面并给缺省值,这里面不能给空指针,strlen会对它进行解引用会崩溃,遇到\0终止,也不能’\0‘,类型不匹配,char转变成了int,也会当成空指针,还是一个崩溃,正确写法"\0",这是一个常量字符串,遇到\0终止,长度为0或者写成" "因为默认都是以\0结束的。
4、拷贝构造函数:如果是默认生成是浅拷贝,如果涉及空间问题就得手动写,因为会指向同一个空间,析构两次,会崩溃,修改还会互相影响。所以要有独立的空间,先把size和capacity放在初始化列表,里面开一个和是s.capacity一样大的空间,最后strcpy。

(3)赋值重载和析构函数

      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;
		}

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

1、赋值重载:赋值同样会涉及到深拷贝和浅拷贝问题,如果被赋值的空间比要赋值的空间大,赋值过去,前者另外的空间会浪费掉,那就先开一个和后者一样大的临时空间,再把旧空间(前者)释放掉,再把开的空间赋值给前者,这样还间接解决了如果被赋值的空间比要赋值的空间小的问题。还有一个问题自己给自己赋值,会出现随机值,因为旧空间释放掉了,会变成随机值,所以加一个if判断语句,如果相等直接返回this。(而且因new失败而破坏旧空间,需要把delete放到new后面)
2、析构函数:先释放_str,并把它置为空,并把size和capacity置为0。

(4)返回重载和打印打符串

        const char* c_str()
		{
			return _str;
		}

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

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

1、打印字符串,直接返回首地址,进行打印。
2、返回值重载:为了能让实例化的对象进行[]访问元素,进行赋值重载,为了防止越界,需要断言一下,返回_str数组对应的下标元素。为了适应传来的const对象并能够进行顺利返回,需要进行函数重载。

(5)字符串字符个数和容量大小函数

       size_t size() const
		{
			return _size;
		}

		size_t capacity() const
		{
			return _capacity;
		}

1、字符个数:直接返回_size,因为实例化对象调用构造函数就算出来_size。
2、容量大小:同理。

(6)字符串比较重载函数

       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 || s == *this;
		}

		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);
		}

1、字符串比较重载函数: 为了能让对象进行比较,进行比较重载,先写出两个>,==,里面直接调用strcmp,其余的进行复用。

(7)字符串两类扩容函数

	  void resize(size_t n, char ch = '\0')
	{
		if (n < _size)
		{
				// 删除数据--保留前n个
			_size = n;
			_str[_size] = '\0';
		}
		else if (n > _size)
		{
			if (n > _capacity)
			{
				reserve(n);
			}
			size_t i = _size;
			while (i < n)
			{
				_str[i] = ch;
				++i;
			}

			_size = n;
			_str[_size] = '\0';
		}
	}

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

			_capacity = n;
			}
	}

1、resize扩容函数:它的特点是开空间初始化,size会随传入的n的大小而变化,如果是<当前的_size会删除数据,反之会补充剩余的字符。用一个if esle语句去解决,如果小于就把_size更新成n,并把最后一个位置置为0,else里面需要看是否扩容,如果需要直接复用reserve,反之用while循环进行插入,最后更新_size。
2、reserve扩容函数: 它的特点是提前开好空间,size不会变,如果传入的n大于当前容量,就异地扩,开辟另一个空间,把_str的内容赋值过去,再释放掉_str,然后把tmp重新赋给_str,再更新容量。

(8)字符串插入函数和删除函数

       void push_back(char ch)
		{
			/*if (_size + 1 > _capacity)
			{
			reserve(_capacity * 2);
			}
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';*/
			insert(_size, ch);
		}
		void push_back(const char* str)
		{
			insert(_size, str);
		}
		string& insert(size_t pos, char ch)
		{
			assert(pos <= _size);
			if (_size + 1 > _capacity)
			{
				reserve(2 * _capacity);
			}
			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				--end;
			}

			_str[pos] = ch;
			++_size;
			return *this;
		}

	   string& insert(size_t pos, const char* str)
	   {
			assert(pos <= _size);
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			// 挪动数据
			size_t end = _size + len;
			while (end > pos + len - 1)
			{
				_str[end] = _str[end - len];
				--end;
			}
			// 拷贝插入
			strncpy(_str + pos, str, len);
			_size += len;
			return *this;

     	 }
        string& erase(size_t pos, size_t len = npos)
       {
		   assert(pos < _size);
		   
		   if (len == npos || pos + len >= _size)
		  {
			  _str[pos] = '\0';
			  _size = pos;
		  }
	      else
	      {
		    strcpy(_str + pos, _str + pos + len);
		    _size -= len;
		  }
			return *this;
	}

1、**push_back函数:**尾插插入用常规思路去写,如果需要扩容,就reserve,反之就进行尾插赋值,最后一个位置置为\0。
第二种就复用insert,因为它是在pos位置前面插入,所以只需要传最后一个位置的参数即可,也就是在\0前面插入就形成了尾插效果。另外为了方便进行字符串尾插,进行函数重载一下即可。
2、insert函数:
插入字符:先看是否能扩容,接下来就是挪动,一般是从最后一个位置开始挪动加上while(end>=pos)条件,可结果出错,这里的end是无符号,无符号end再–,减到-1,被当成全1,是无符号最大值,造成错误,同样这里也不能改成int,因为会发生隐式转换,有符号变成无符号,除非上面参数pos也改成int,就能解决或者把pos强转int。但是这是模拟实现,下标都常用无符号。
解决:可以把end位置往后挪一位,也即从最后一个位置的下一个位置开始挪动。把end-1的数赋给end,这样end等于pos的时候就停止,而且第一个位置也已经被挪动。就能成功运行。再进行赋值并更新_size。
插入字符串 :大致思路和上面一样,注意的是终止条件并不是pos位置,如果是pos位置的话会发生越界,因为字符串已经挪完,end还没有到pos,end是从end+len的位置开始的。所以这里应该是挪完的时候在pos+len,停的位置就再减一。最后空出的位置直接进行字符串拷贝,更新_size。
3、erase函数: 删除分为两种情况,一种是pos+len长度大于等于size,意思是把pos位置之后的全删掉,那就把pos位置置为\0,在更新_size。
第二种是长度小于size,就把后面的数据往前挪动,可以用拷贝函数进行从前往后覆盖,直接注意的是len == npos要单独写,否则会溢出,因为如果n是npos,已经是最大值了,再加就会绕回去,所以防止用缺省值和pos相加出错,分开来写。

(9)字符串拼接和拼接重载函数

       void append(const char* str)
		{
			//size_t len = strlen(str);
			//if (_size+len > _capacity)
			//{
			//	reserve(_size + len);
			//}
			strcat(_str, str);
			//_size += len;
			insert(_size, str);
		}

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

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

1、append拼接函数:两种方法,一种是算长度,看是否要扩容,然后直接调用字符串追加函数strcat进行追加,另一种是复用insert。
2、拼接重载函数:字符重载函数,直接复用push_back,字符串重载函数复用append和push_back都可行。

(10)字符串交换和查找函数及清理函数

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

		size_t find(char ch, size_t pos = 0)
		{
			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)
		{
			assert(pos < _size);
			char* p = strstr(_str + pos, str);
			if (p == nullptr)
			{
				return npos;
			}
			else
			{
				return p - _str;
			}
		}

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

1、交换函数
string库里面的交换函数是void swap(string& x, string& y),里面需要创建临时变量进行赋值,发生了三次深拷贝,而这模拟实现可以是void swap(string& s),在里面调用C++标准库里面的swap把三个成员换一下就可以,效率可以提升很多。
2、查找函数:对于字符查找函数,用for循环进行遍历,如果找了就返回下标,否则返回npos。
对于字符串查找函数,直接调用子串搜索函数并返回它的首地址,如果找不到返回npos,否则返回它的地址减去首地址。
3、清0函数:把size的位置置为\0,并把size置为0和流插入函数结合用。

(11)流插入和流提取的重载函数

ostream& operator<<(ostream& out, const string& s)
{
	for (auto ch : s)
	{
		out << ch;
	}
	return out;
}
istream& operator>>(istream& in, string& s)
{
	s.zero();
	char ch = in.get();
	char tmp[128];
	size_t i = 0;
	while (ch != ' ' && ch != '\n')
	{
		tmp[i++] = ch;
		if (i == 127)
		{
			tmp[127] = '\0';
			s += tmp;
			i = 0;
		}
		    ch = in.get();
	}
	   if (i != 0)
	  {
		   tmp[i] = '\0';
		   s += tmp;
	  }
	       return in;
}

这两个函数不能写到里成员函数,因为this指针就把第一个位置抢了,cout做不了第一个左操作数,得写成全局,不一定要用友元函数。
流插入函数: 直接cout<<s,和cout<<c_str,有区别,后者遇到\0停止,里面直接用for循环进行打印。
流提取函数:1、C/C++规定输入多个字符,会把\n和空格当成多个字符之间的间隔,cin流对象缓冲区会拿不到\n和空格,用get就不会把它两当成间隔,把它们拿到,wihle就能终止。
2、流提取前要清理一下,设置\0,防止出现乱码。
3、如果插入的字符串很长啊,+=会频繁扩容,先创建一个tmp临时数组,输入一个字符,填到数组里,如果快要填满的时候,就在最后填上\0,把这个数组加到s上去,i置为0。每满一次往s上加。如果最后i不等于0,就直接就把tmp i的位置置为\0,最后还要判断一下i是否为0,如果不为0,说明tmp里面还没有加进去完。

四、string类模拟实现代码

(1)simulate_string.h

#pragma once
#include<assert.h>
namespace mould
{
	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 == 0 ? 3 : _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

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

		// s1 = s3;
		// s1 = s1;
		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;
		}

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

		const char* c_str()
		{
			return _str;
		}

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

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

		size_t size() const
		{
			return _size;
		}

		size_t capacity() const
		{
			return _capacity;
		}

		// 不修改成员变量数据的函数,最好都加上const
		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 || s == *this;
		}

		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)
			{
				// 删除数据--保留前n个
				_size = n;
				_str[_size] = '\0';
			}
			else if (n > _size)
			{
				if (n > _capacity)
				{
					reserve(n);
				}

				size_t i = _size;
				while (i < n)
				{
					_str[i] = ch;
					++i;
				}

				_size = n;
				_str[_size] = '\0';
			}
		}

		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)
		{
			
			insert(_size, ch);
		}
		void push_back(const char* str)
		{
			insert(_size, str);
		}

		void append(const char* str)
		{
			
			insert(_size, str);
		}

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

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

		string& insert(size_t pos, char ch)
		{
			assert(pos <= _size);
			if (_size + 1 > _capacity)
			{
				reserve(2 * _capacity);
			}


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

			_str[pos] = ch;
			++_size;

			return *this;
		}

		string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size);

			size_t len = strlen(str);

			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}

			// 挪动数据
			size_t end = _size + len;
			while (end > pos + len - 1)
			{
				_str[end] = _str[end - len];
				--end;
			}

			// 拷贝插入
			strncpy(_str + pos, str, len);
			_size += len;

			return *this;

		}

		string& erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);

			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}

			return *this;
		}

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

		size_t find(char ch, size_t pos = 0)
		{
			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)
		{
			assert(pos < _size);

			// kmp
			char* p = strstr(_str + pos, str);
			if (p == nullptr)
			{
				return npos;
			}
			else
			{
				return p - _str;
			}
		}

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

	private:
		char* _str;
		size_t _capacity;
		size_t _size;
		static const size_t npos;
	};
	const size_t mould::string::npos = -1;
}
	
	

(2)test.cpp

#include<iostream>
#include"simulate_string.h"
using namespace std;
void Print(const mould:: string& s)
{
	for (size_t i = 0; i < s.size(); ++i)
	{
		cout << s[i] ;
	}
	cout << endl;
	mould::string::const_iterator it = s.begin();
	while (it != s.end())
	{
		//*it = 'x';
		++it;
	}
	for (auto ch : s)
	{
		cout << ch ;
	}
}
ostream& operator<<(ostream& out, const string& s)
{
	for (auto ch : s)
	{
		out << ch;
	}
	return out;
}
istream& operator>>(istream& in, string& s)
{
	s.clear();
	char ch = in.get();
	char tmp[128];
	size_t i = 0;
	while (ch != ' ' && ch != '\n')
	{
		tmp[i++] = ch;
		if (i == 127)
		{
			tmp[127] = '\0';
			s += tmp;
			i = 0;
		}

		ch = in.get();
	}
	if (i != 0)
	{
		tmp[i] = '\0';
		s += tmp;
	}
	return in;
}


void _string1()
{
	mould::string s1("power latent");
	cout << s1.c_str() << endl;
	s1[0]++;
	cout << s1.c_str() << endl;
	cout << endl;
}
void _string2()
{

	mould::string s2("power latent");
	mould::string s3(s2);
	mould::string s1;
	cout << s2.c_str() << endl;
	cout << s3.c_str() << endl;

	s2[0]++;
	cout << s2.c_str() << endl;
	cout << s3.c_str() << endl;

	s1 = s2;
	cout << s1.c_str() << endl;
	cout << s2.c_str() << endl;
	cout << endl;

}
void _string3()
{
	mould::string s1("power latent");
	for (size_t i = 0; i < s1.size(); ++i)
	{
		s1[i]++;
	}
	for (size_t i = 0; i < s1.size(); ++i)
	{
		cout << s1[i];
	}
	cout << endl;
	Print(s1);
	mould::string::iterator it = s1.begin();
	while (it != s1.end())
	{
		(*it)--;
		++it;
	}
	cout << endl;

	it = s1.begin();
	while (it != s1.end())
	{
		cout << *it ;
		++it;
	}
	cout << endl;

	for (auto ch : s1)
	{
		cout << ch ;
	}
	cout << endl;
	cout << endl;

}
void _string4()
{
	mould::string s1("power latent");
	mould::string s2("power latent");
	mould::string s3("aa");

	cout << (s1 < s2) << endl;
	cout << (s1 == s2) << endl;
	cout << (s1 >= s2) << endl;
	cout << endl;
}

void _string5()
{
	mould::string s1("power latent");
	s1 += ' ';
	s1 += "ssssssssssssssssssssssssssss";

	cout << s1.c_str() << endl;

	s1.insert(6, 'c');
	cout << s1.c_str() << endl;

	s1.insert(0, 'q');
	cout << s1.c_str() << endl;
	cout << endl;
}

void _string6()
{
	mould::string s1("hello world66666666666");
	cout << s1.capacity() << endl;
	s1.reserve(10);
	cout << s1.capacity() << endl;
	cout << endl;
}
void _string7()
{
	mould::string s1;
	s1.resize(10, 'k');
	cout << s1.c_str() << endl;
	s1.resize(15, 'd');
	cout << s1.c_str() << endl;

	s1.resize(3);
	cout << s1.c_str() << endl;
	cout << endl;
}
void _string8()
{
	mould::string s1("6666666");
	s1.insert(0, 'a');
	cout << s1.c_str() << endl;

	s1.insert(2, 'a');
	cout << s1.c_str() << endl;

	s1.insert(3, "bbb");
	cout << s1.c_str() << endl;

	s1.insert(1, "bbb");
	cout << s1.c_str() << endl;
	cout << endl;
}
void _string9()
{
	mould::string s1("0123456789");
	cout << s1.c_str() << endl;

	s1.erase(4, 3);
	cout << s1.c_str() << endl;

	s1.erase(4, 30);
	cout << s1.c_str() << endl;

	s1.erase(2);
	cout << s1.c_str() << endl;
	cout << endl;
}
void _string10()
{
	string s1("0123456789");
	s1 += '\0';
	s1 += "xxxxxxxx";

	  cout<<s1<<endl ;
	 cout<<s1.c_str() << endl;

	string s2;
	cin>>s2;
	cout<<s2<< endl;

	cin>>s1;
	cout<<s1<< endl;
}

int main()
{
	_string1();
	_string2();
	_string3();
	_string4();
	_string5();
	_string6();
	_string7();
	_string8();
	_string9();
	_string10();
	return 0;
}

(3)运行结果

在这里插入图片描述

五、深浅拷贝及其他说明

浅拷贝:也叫位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还在,所以当继续对资源进项操作时,就会发生发生了非法访问。
深拷贝:可以用深拷贝解决浅拷贝问题,每个对象都有一份独立的资源,不要和其他对象共享,如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。保证多个对象不会因共享资源造成多次释放造成程序崩溃问题。
vs下string的结构
string总共占28个字节,内部结构复杂。
1、先是有一个联合体,联合体用来定义string中字符串的存储空间:当字符串长度小于16时,使用内部固定的字符数组来存放,当字符串长度大于等于16时,从堆上开辟空间。
2、Mysize存字符串有效长度。
3、Myres存空间容量。
4、还有一个指针。
g++下string的结构:g++下,string是通过写时拷贝实现的,string对象总共占4个字节(64位下是8个字节),内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:
1、空间总大小
2、字符串有效长度
3、引用计数

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

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

相关文章

纯x86汇编实现的多线程操作系统实践 - 第三章 BSP的守护执行

本章我们将详细讲解BSP剩下的执行代码&#xff0c;它们被安排在bp_32.asm文件中。bp_32.asm主要完成以下功能&#xff1a;系统中断初始化加载字符图形数据到内存区域将AP的启动代码和32位保护模式下的代码分别加载到内存中显示主界面以及系统启动信息向所有AP群发启动命令进入守…

linux 解压.gz文件 报错 gzip:stdin:not in gzip format(已解决)

目录 1、问题&#xff1a; 2、分析原因 3、解决办法 1、问题&#xff1a; 在解压一个以【.gz】&#xff08;注意不是.tar.gz&#xff09;结尾的压缩包时&#xff0c;遇到报错 【gzip&#xff1a;stdin&#xff1a;不是gzip格式】 翻译一下问题&#xff1a;【gzip&#xff1a;st…

纯x86汇编实现的多线程操作系统实践 - 第一章 系统整体结构说明

现代CPU都是多核系统&#xff0c;拥有多个执行内核&#xff08;即计算引擎&#xff09;&#xff0c;可并发执行不同的代码。在CPU众多的执行内核中&#xff0c;有一个为主执行内核&#xff08;BSP&#xff09;&#xff0c;在CPU上电后&#xff0c;该主执行内核会率先启动&#…

lighthouse-自定义Gatherer与Audits

这篇文章是Lighthouse的后续&#xff0c;之前介绍了 lighthouse的介绍和基本使用方法 Lighthouse组合Puppeteer检测页面 这两篇文章&#xff0c;在这两篇文章中介绍了lighthouse的整体架构和基本运行的逻辑&#xff0c;lighthouse默认也采集了足够丰富的数据供我们去分析页面的…

都在用 AI 生成美少女,而我却。。。

最近 AI 画画特别的火&#xff0c;你能从网上看到非常多好看的图片&#xff0c;于是我就开始了我的安装之旅&#xff0c;我看到的图是这样的。这样的。还有这样的。然后我就开始了我的 AI 安装生成计划。安装环境首先我们需要安装 Python 环境&#xff0c;因为这个需要显卡&…

NCRE计算机等级考试Python真题(二)

第二套试题1、关于算法的描述&#xff0c;以下选项中错误的是A.算法具有可行性、确定性、有穷性的基本特征B.算法的复杂度主要包括时间复杂度和数据复杂度C.算法的基本要素包括数据对象的运算和操作及算法的控制结构D.算法是指解题方案的准确而完整的描述正确答案&#xff1a; …

Java基础之日志

2.日志 2.1概述【理解】 概述 程序中的日志可以用来记录程序在运行的时候点点滴滴。并可以进行永久存储。 日志与输出语句的区别 输出语句日志技术取消日志需要修改代码&#xff0c;灵活性比较差不需要修改代码&#xff0c;灵活性比较好输出位置只能是控制台可以将日志信息写…

用于C++的对象关系映射库—YB.ORM

1 介绍YB.ORM YB.ORM 旨在简化与关系数据库交互的 C 应用程序的开发。 对象关系映射器(ORM) 通过将数据库表映射到类并将表行映射到应用程序中的对象来工作&#xff0c;这种方法可能不是对每个数据库应用程序都是最佳的&#xff0c;但它被证明在需要复杂逻辑和事务处理的应用程…

不怕被AirTag跟踪?苹果Find My技术越来越普及

苹果的 AirTag 自推出以来&#xff0c;如何有效遏制用户用其进行非法跟踪&#xff0c;是摆在苹果面前的一大难题。一家为执法部门制造无线扫描设备的公司近日通过 KickStarter 平台&#xff0c;众筹了一款消费级产品&#xff0c;可帮助用户检测周围是否存在追踪的 AirTag 等设备…

Spring中的FactoryBean 和 BeanFactory、BeanPostProcessor 和BeanFactoryPostProcessor解析

文章目录FactoryBean 和 BeanFactory后置处理器BeanPostProcessor 和 BeanFactoryPostProcessorBeanPostProcessorBeanFactoryPostProcessorFactoryBean 和 BeanFactory BeanFactory接⼝是容器的顶级接⼝&#xff0c;定义了容器的⼀些基础⾏为&#xff0c;负责⽣产和管理Bean的…

python元编程详解

什么是元编程 软件开发中很重要的一条原则就是“不要重复自己的工作&#xff08;Don’t repeat youself&#xff09;”&#xff0c;也就是说当我们需要复制粘贴代码时候&#xff0c;通常都需要寻找一个更加优雅的解决方案&#xff0c;在python中&#xff0c;这类问题常常会归类…

C++015-C++函数

文章目录C015-C函数函数目标char[]和stringchar[]char*string字符常量与字符串常量字符串的输入题目描述 字符串输出题目描述在线练习&#xff1a;总结C015-C函数 在线练习&#xff1a; http://noi.openjudge.cn/ https://www.luogu.com.cn/ 函数 目标 函数是指一段可以直接被…

SVG实例详解系列(一)(svg概述、位图和矢量图区别(图解)、SVG应用实例)

SVG实例详解系列&#xff08;一&#xff09; (svg概述、位图和矢量图区别&#xff08;图解&#xff09;、SVG应用实例&#xff09; 目录 一、什么是SVG? &#xff08;1&#xff09;、位图和矢量图概念&#xff08;图解&#xff09; &#xff08;2&#xff09;、SVG的小例子…

Flutter入门进阶之旅 -开源Flutter项目

开源Flutter项目 该项目为纯flutter端项目&#xff0c;采用aar方式寄生在原生APP中&#xff0c;作为APP中的一个独立模块 在业务逻辑上做到与原生APP完全隔离&#xff0c;Flutter端开发者&#xff0c;可完全不用关注原生端的业务模块 两端开发彼此业务隔离&#xff0c;缩小了对…

数字IC手撕代码--小米科技(除法器设计)

前言&#xff1a; 本专栏旨在记录高频笔面试手撕代码题&#xff0c;以备数字前端秋招&#xff0c;本专栏所有文章提供原理分析、代码及波形&#xff0c;所有代码均经过本人验证。目录如下&#xff1a;1.数字IC手撕代码-分频器&#xff08;任意偶数分频&#xff09;2.数字IC手撕…

wondows10系统python2.7兼容安装python3.10

假设已安装好python2.7和pyhon3.10。 python命令只需要应用程序改名即可&#xff0c;需要修改的有python.exe和pythonw.exe pip命令麻烦点&#xff0c;需要用改名后的程序名 重新安装&#xff0c;命令如下&#xff1a; python3 -m pip install --upgrade pip --force-reinst…

说说 Pluma 插件管理框架

1. 概述 Pluma 是一个用 C 开发的可用于管理插件的开源架构&#xff0c;其官网地址为&#xff1a;http://pluma-framework.sourceforge.net/。该架构是个轻量级架构&#xff0c;非常易于理解。 Pluma 架构有以下基本概念&#xff1a; 1&#xff09;插件的外在行为体现为一个…

【C++的OpenCV】第七课-OpenCV图像常用操作(四):图像形态学-图像侵蚀和扩散的原理

让我们来深化前边学习的内容前言一、图像形态学是什么&#xff1f;二、侵蚀和扩张的原理2.1 图像的侵蚀2.1.1 概念2.1.2 原理解释2.2 图像的扩张2.2.1 概念2.2.2 原理解释相关链接&#xff1a;【C的OpenCV】第六课-OpenCV图像常用操作&#xff08;三&#xff09;&#xff1a;Op…

RK3568镜像的拆包和打包

文章目录 前言一、window上分包和打包分包打包二、Linux上分包和打包分包打包总结前言 本文记录在win10上利用瑞芯微提供的工具进行分包和打包,同样也有Linux教程 提示:以下是本篇文章正文内容,下面案例可供参考 一、window上分包和打包 分包 window下一般直接利用工具即…

【正点原子FPGA连载】 第十八章基于BRAM的PS和PL的数据交互 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

1&#xff09;实验平台&#xff1a;正点原子MPSoC开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id692450874670 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第十八章基于BRA…