详解string容器(应用+模拟实现,string练习题)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/liuyuchen282828/article/details/102748152

为什么要有string容器

string:其实就是一个字符串,c++对字符串进行了封装的,封装到一个类里面,这样用户就不用担心开辟空间的问题,只需要往string类里放字符串就可以了,string其实还可以自增长
很多人就会有一个疑问,以前在c语言中,已经有字符串了,其实c语言中表示字符串就是char*,也就是字符数组+'\0'

string应用

string类的应用

string类常用接口

构造

在这里插入图片描述

	string s1;
	string s2("hello");
	string s3(10, '$');
	string s4(s3);

容量

void TestString()
{
	string s("hello");
	cout << s.size() << endl;    //size和length都是求字符串的有效个数
	cout << s.length() << endl;
	cout << s.capacity() << endl;

	if (s.empty())
		cout << "NULL" << endl;
	else
		cout << "NOT NULL string" << endl;

	//只清空string类中有效字符个数,不会改变底层空间的大小
	s.clear();		//不会清空容量
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	if (s.empty())
		cout << "NULL" << endl;
	else
		cout << "NOT NULL string" << endl;
}

在这里插入图片描述

resize()

void resize(size_t n, char ch):功能—将string类中的有效字符改变到n个,多的字符采用ch进行填充
注意

  1. 将string类中有效元素缩小,只改变有效元素的个数,不会改变底层空间的大小
  2. 如果是将string类中有效元素增多,可能需要扩容
void TestString3()
{
	string s("hello");
	cout << s << endl;
	cout << s.size() << endl;
	cout << s.capacity() << endl;

	//resize 10个有效字符,原来有5个,多出来的5个用!号进行填充
	s.resize(10,'!');
	cout << s << endl;
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	}
reserve()
void reserve(size_t newcapacity)
  1. newcapacity > oldcapacity(string 类旧空间增多---->容量改变(最终容量大小) >= newcapacity)
  2. newcapacity < oldcapacity(string 类旧空间缩小---->该函数直接返回,除非newcapacity < 15,缩小到15
    注意:
    只改变容量大小,不会改变有效元素个数
void TestString4()
{
	string s("hello");
	cout << s.size() << endl;
	cout << s.capacity() << endl;

	s.reserve(20);
	cout << s.size() << endl;
	cout << s.capacity() << endl;

	s.reserve(40);
	cout << s.size() << endl;
	cout << s.capacity() << endl;

	s.reserve(24);
	cout << s.size() << endl;
	cout << s.capacity() << endl;

	s.reserve(5);
	cout << s.size() << endl;
	cout << s.capacity() << endl;
}

在这里插入图片描述
string类维护了一个空间16字节

元素访问

在这里插入图片描述

void TestString5()
{
	string s("hello");
	cout << s[0] << endl;
	s[0] = 'H';
	//[] 如果越界----assert触发
	


	cout << s.at(2) << endl;
	s.at(2) = 'L';
	//s.at()如果越界---抛出out_of_range的异常

	//不同点
	//cout << s[10] << endl;  //越界访问

	cout << s.at(10) << endl;
}
cout << s[10] << endl;  //越界访问

在这里插入图片描述

cout << s.at(10) << endl;//越界访问

在这里插入图片描述

元素修改

在这里插入图片描述

void TestString6()
{
	string s1;
	s1.push_back('I');

	s1 += " Love ";

	string s2("you");
	s1 += s2;

	s1.append(1, ',');
	s1.append("祖国");
	s1.append(3, '!');

	cout << s1 << endl;
}

在这里插入图片描述

string类扩容机制
  1. vs—PJ----1.5倍扩容
  2. Linux-----SGI----2倍扩容
  3. 如果提前直到大概要往string类存放多少个元素,可以通过reserve将空间给好
void TestPushBack() 
{ 
	string s;       //维护一个数组,最多放15个有效字符
	size_t sz = s.capacity();    
	cout << "making s grow:\n";    
	for (int i = 0; i < 100; ++i)    
	{ 
		s.push_back( 'c');        
		if (sz != s.capacity())        //sz记录上一次容量大小
		{ 
			sz = s.capacity();            
			cout << "capacity changed: " << sz << '\n'; 
		} 
	} 
}

在这里插入图片描述

字符串特殊操作

在这里插入图片描述

void TestString7()
{
	string s("123456");
	int ret = atoi(s.c_str());

}

//find rfind
void TestString8()
{
	string s("hello world");
	size_t pos = s.find( ' ');
	if (pos != string::npos)
	{
		cout << ' ' << "is in s"<<endl;
	}

	pos = s.find("world");
	if (pos != string::npos)
	{
		cout << "world" << "is in s" << endl;
	}

	//获取文件后缀
	string ss("2019-10-26.cpp.cpp");
	pos = ss.rfind('.') + 1;  //后缀的位置从.的后面开始
	cout << pos << endl;

	string filepos = ss.substr(pos);
	cout << filepos << endl;

}

在这里插入图片描述

迭代器(string中很少用到)

三种遍历方法
void TestString9()
{
	string s("hello");

	for (auto e : s)
	{
		cout << e;
	}
	cout << endl;

	for (int i = 0; i < s.size(); ++i)
		cout << s[i];
	cout << endl;

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

反转字符串

一个函数调用就可以

//反转字符串
void reversestring(string &s)
{
	//char* begin = (char *)s.c_str();
	//char* end = begin + s.size() - 1;
	//while (begin < end)
	//{
	//	swap(*begin, *end);
	//	begin++;
	//	end--;
	//}

	reverse(s.begin(), s.end());
}

字符串中第一个唯一字符

字符做为数组下标

class Solution {
public:
    int firstUniqChar(string s) {
        //1.统计每个字符出现的次数
        int count[256]={0};
        for(auto e:s)
        {
            count[e]++;
        }

        //2.找第一个只出现一次的字符
        for(size_t i = 0; i < s.size(); ++i)
        {
            if(count[s[i]] == 1)
                return i;
        }
        return -1;
    }
};

字符串最后一个单词的长度

cin用来接受字符串,如果字符串中出现空格,换行,是无法拿到整个字符串的

#include<iostream>
#include<string>
using namespace std;

int main()
{
    string s;
    getline(cin,s);
    //找到最后一个单词的位置
    cout<< s.substr(s.rfind(' ')+1).size();
    return 0;
}

字符串相加

在这里插入图片描述
大数相加,无法用普通的类型进行保存
可以把这些数据看成是字符串

class Solution {
public:
    string addStrings(string num1, string num2) {
        
         int LSize = num1.size();        
         int RSize = num2.size();                
         // 以长字符串作为外部循环        
         if(LSize < RSize)        
         {            
             num1.swap(num2);            
             swap(LSize, RSize);        
        } 

         string strRet;        
         strRet.reserve(LSize+1);                
         char cRet = 0;        
         char cstep = 0;

         for(size_t i = 0; i < LSize; ++i)  
          {            
              cRet = num1[LSize - i - 1] - '0' + cstep;            
              cstep = 0;                        
              if(i < RSize)            
              {                
                  cRet += num2[RSize - i - 1] - '0';           
              }                       
                    if(cRet >= 10)            
                    {               
                        cRet -= 10;               
                        cstep = 1;           
                    }                       
                        strRet += cRet + '0';        
           } 

            if(cstep)            
            strRet += '1';                
            reverse(strRet.begin(), strRet.end());                
            return strRet;    
            } 
};

string模拟实现

string模拟实现

如何实现

string是动态管理字符串,不论多少个数,都可以管理,编译器中会有一个静态数组,刚开始大小为16字节,有效元素个数为15个,所以当有效字符小于15个时,直接用静态数组存放,效率高一些


我们实现的string只给出动态数组,维护一个char*指针,创建对象开辟空间即可

构造

有参构造
用户在创建对象时,一般有参的话,都会给出c风格的字符串,所以我们的参数,就是一个char*的指针,最好设个默认值为空。然后在有参构造里做两件事情,一个是申请空间,另外一个就是拷贝数据
但是要注意用户一般很傻,有可能会传一个空的指针进来,会引起代码崩溃。所以我们一定要对传来的数据进行判空处理,如果为空,则初始化为空串即可。

string(char *str = "")
		{
			//如果指针为空,则初始化位空字符串
			if (nullptr == str)
				str = "";

			//申请空间
			_str = new char[strlen(str) + 1];

			//
			strcpy(_str, str);
		}

拷贝构造
系统会给出默认的拷贝构造,但是存在浅拷贝问题
什么是浅拷贝?
浅拷贝可能通过默认的拷贝构造发生,也有可能通过编译器默认的赋值运算符的重载发生。
所以我们在拷贝构造和赋值时,让每个对象都要拥有一份独立的资源

string(const string& s)
		:_str(new char[strlen(s._str)+1])  //开辟空间
	{
		strcpy(_str, s._str);
	}

赋值运算符重载

  1. 开辟新空间
  2. 拷贝元素
  3. 释放旧空间
  4. 指向新空间
string& operator=(const string& s)
	{
		//自己给自己赋值,不用做任何操作
		if (this != &s)
		{
			//不是自己给自己赋值
			//1.申请新空间
			char *temp = new char[strlen(s._str) + 1];
			//2.拷贝数据
			strcpy(temp, s._str);
			//3.释放原来空间
			delete[]_str;
			//4.原空间指针指向新空间
			_str = temp;
		}
		return *this;
	}

析构

看一下当前对象指针有没有资源,如果有,释放掉,随后指针指向空,防止野指针。

~string()
		{
			if (_str)
				delete[]_str;
			_str = nullptr;
		}

以上的是模拟实现的一个string的版本

另外一个版本实现string

资源的转移

namespace bite
{
	class string
	{
	public:
		string(char *str = "")
		{
			//如果指针为空,则初始化位空字符串
			if (nullptr == str)
				str = "";

			//申请空间
			_str = new char[strlen(str) + 1];

			strcpy(_str, str);
		}

		string(const string& s)
			:_str(nullptr)
		{
			string strTemp(s._str);
			swap(_str, strTemp._str);
			
		}

		string& operator=(const string& s)
		{
			//自己给自己赋值,不用做任何操作
			if (this != &s)
			{
				string strTemp(s._str);
				swap(_str, strTemp._str);
			}
			return *this;
		}

		~string()
		{
			if (_str)
				delete[]_str;
			_str = nullptr;
		}


		//编译器生成的默认赋值运算符重载存在浅拷贝,而且会有资源泄露,没有释放资源
	private:
		char * _str;
	};

}

此版本有一个缺陷,就是创立的临时对象的地址不为空,则会引起代码崩溃,在vs2013编译器下这个样实现的string没有问题,因为vs2013默认值为空,其他编译器给的默认值可能是随机值。我们可以直接在拷贝构造函数加一个参数列表,给临时对象赋值为空

还有一种引用计数的方式,采用引用计数来解决浅拷贝的问题
写时拷贝

猜你喜欢

转载自blog.csdn.net/liuyuchen282828/article/details/102748152