【C++】string


1. 简单了解string

  1. string是表示字符串的字符类,其底层是basic_string模板类的实例化,只能存储单字节的字符串,不能操作多字节和变长字符的序列。
  2. 在使用string类时,必须包含头文件< string >以及展开命名空间using namespace std。

2. string的常用接口

  1. 构造函数
    在这里插入图片描述

a. 用法
(1)默认构造,构造空字符串。(2)拷贝构造。(3)从str中取出子串放入string中。(4)用字符串来构造string的对象。(5)拷贝前n个字符到string对象。(6)将n个相同的字符填充到string对象中。(7)利用迭代器(类似于指针)将该范围内的字符拷贝到string的对象中。
注意
第3点len的缺省值是npos。npos是静态成员变量,表示整形的最大值。当你不传len时,默认从pos位置到字符串结束。

b. 例子
这里只讲四种常见的(用长方形框出来的)。
在这里插入图片描述

  1. 赋值
    在这里插入图片描述

例子
在这里插入图片描述
3. 访问
在这里插入图片描述

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

  1. 迭代器 在这里插入图片描述

先讲前两种
例子

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
注意
iterator是像指针一样的类型,有可能是指针,也有可能不是指针。但底层都是指针实现。string底层就是指针。
对比
在这里插入图片描述
相比operator[],迭代器好像更加复杂。但是言之过早,以下有更方便的范围for。
意义
(1)范围for不比operator[]方便?但这又和迭代器有什么关系?
在这里插入图片描述
通过查看范围for的反汇编代码,发现底层是迭代器。在这里插入图片描述
范围for底层就是迭代器,一个类支持迭代器,就支持范围for。
(2)任何容器都支持迭代器,且用法是类似的。
在这里插入图片描述
(3)算法可以配合迭代器处理容器内数据。
在这里插入图片描述
在这里插入图片描述

  1. 反向迭代器
    在这里插入图片描述

例子
在这里插入图片描述

在这里插入图片描述

  1. const修饰的迭代器
    在这里插入图片描述
    返回由const修饰的迭代器,只能读不能写。

例子在这里插入图片描述

  1. 容量
    在这里插入图片描述

(1)size()和length()返回字符串的长度(字节个数)。
(2)capacity()返回字符串所占空间的大小。
(3)clear()清理字符串的内容。
(4)empty()判断字符串是否是空串,是就返回真,不是就返回假。
在这里插入图片描述
(5)reserve:预定,保留(不同于reverse:逆置。)
在这里插入图片描述
在这里插入图片描述
(6)resize
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
缩容是有代价的,string的缩容很保守(就像free和delete不会单独释放空间的一部分),实际上的缩容是先开一片空间,按规定大小的空间拷贝过去,再释放旧空间,是以时间换空间的。
总结
想开空间同时初始化,用resize(),想先把空间开好,用reserve()。

(7)shrink_to_fit:收缩至合适
在这里插入图片描述
但是没必要缩容,以时间换空间,因为现代计算机的内存足够大。

  1. 追加
    在这里插入图片描述
    在这里插入图片描述

append与+=都有追加的作用,但是+=更实用,所以只讲+=。
例子
在这里插入图片描述

  1. 尾插和插入
    在这里插入图片描述
    在这里插入图片描述

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

  1. 删除
    在这里插入图片描述

注意
(1)若len不传参,则用缺省值,将字符串从pos位置删除至结尾。
(2)迭代器区间一般都是左闭右开的,如[first,last)。
例子
在这里插入图片描述

  1. c_str得到字符数组的指针
    在这里插入图片描述

注意
得到的字符数组是该string对象的字符串。
例子
在这里插入图片描述

  1. find查找
    在这里插入图片描述
    注意
    找到后,返回的是找到字符串的首字符的下标。

例子
给一个网址,要求用协议、域名、资源名分割。

//https://legacy.cplusplus.com/reference/string/string/
find
int main()
{
	string s1 = "https://legacy.cplusplus.com/reference/string/string/";
	string protocol;//协议
	string domain;//域名
	string uri;//资源
	
	//找协议
	size_t pos1 = s1.find("://");
	if (pos1 != string::npos)
	{
		//找到了,取子串
		protocol = s1.substr(0, pos1);//pos1恰好是协议的长度
	}


	//找域名
	size_t pos2 = s1.find('/', pos1 + 3);//从pos+3的位置开始找
	if (pos2 != string::npos)
	{
		//找到了
		domain = s1.substr(pos1 + 3, pos2 - (pos1 + 3));
	}

	//找资源
	uri = s1.substr(pos2 + 1);//直接从pos2+1位置开始取到结束
	
	cout << protocol << endl;
	cout << domain << endl;
	cout << uri << endl;
	cout<<s1<<endl;
	return 0;
}

运行结果
在这里插入图片描述

  1. rfind与find功能相同,但是是从后往前找。
    在这里插入图片描述

注意
(1)pos不传参,用缺省值npos,表示从最后一个字符往前找。
(2)从pos位置开始找,是包括pos位置的。

例子
字符串最后一个字符的长度

#include <iostream>
using namespace std;
#include<string>
int main()
{
    //从后面开始找,有两种情况:1.找到空格;2.没找到空格
    string str;
    //读取字符串
    getline(cin,str);
    size_t pos = str.rfind(' ');
    //找到了
    if(pos!=string::npos)
    {
        cout<<str.size()-(pos+1)<<endl;
    }
    else
    {
        cout<<str.size()<<endl;
    }
    return 0 ;
}

问题
为什么不直接用cin而是用getline?cin遇到空格和读取就结束,不读取空格。比如当你输入abc abc时,只输入abc到str,所以用getline。

  1. getline
    在这里插入图片描述

  2. to_string
    在这里插入图片描述

例子
在这里插入图片描述
不管是float还是double都默认打印到小数点后六位。


3. 简单模拟实现string

#include<assert.h>
using namespace std;
namespace zn
{
	class string
	{
	private:
		size_t _size;
		size_t _capacity;
		char* _str;
		static size_t npos;
	public:
		//构造函数
		//注意:初始化列表按照成员的声明顺序初始化
		string(const char*s = "")
			:_size(strlen(s))
			,_capacity(_size)
			,_str(new char[_capacity+1])
		{
			memcpy(_str, s, _size+1);
		}
		//拷贝构造
		string(const string& s)
		{
			_str = new char[s._capacity+1];
			for (size_t i = 0; i < s._size; i++)
			{
				push_back(s[i]);
			}
			_str[_size] = '\0';
		}
		//法二
		/*string(const string& s)
		{
			_str = new char[s._capacity + 1];
			memcpy(_str, s._str, s._size + 1);
			_size = s._size;
			_capacity = s._capacity;
		}*/

		//赋值
		string& operator=(const string& s)
		{
			//法1
			if (this != &s)
			{
				char* tmp = new char[s._capacity + 1];
				memcpy(tmp, _str, _size + 1);
				delete[] _str;
				_str = tmp;
				_size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}
		//法2
		//传值传参,调用拷贝,s出来作用域会销毁
		//交换后,s指向旧空间,不用自己销毁,出了作用域会自动销毁
		//string& operator=(string s)
		//{
		//	if (this != &s)
		//	{
		//		//不能直接交换this和&s,因为swap用到赋值,会引起无穷递归
		//		std::swap(_str, s._str);
		//		std::swap(_size, s._size);
		//		std::swap(_capacity, s._capacity);
		//	}
		//	return *this;
		//}
		//法3
		//将交换this和&s封装成函数
		/*void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size,s._size);
			std::swap(_capacity,s._capacity);
		}
		string& operator=(string s)
		{
			swap(s);
			return *this;
		}*/

		//析构函数
		~string()
		{
			delete[]_str;
			_str = nullptr;
			_size = _capacity = 0;
		}
		//c_str
		const char* c_str()const
		{
			return _str;
		}
		//size
		size_t size()const
		{
			return _size;
		}
		//operator[]
		char& operator[](size_t pos)
		{
			//检查
			assert(pos < _size);
			return _str[pos];
		}
		const char& operator[](size_t pos)const
		{
			assert(pos < _size);
			return _str[pos];
		}
		//普通迭代器
		//注意:迭代器区间是左闭右开[begin(),end())
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		//const迭代器
		typedef const char* const_iterator;
		const_iterator begin()const
		{
			return _str;
		}
		const_iterator end()const
		{
			return _str + _size;
		}
		//容量
		size_t capacity()
		{
			return _capacity;
		}
		//扩容
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				char* tmp = new char[n+1];
				memcpy(tmp, _str, _size + 1);
				delete [] _str;
				_str = tmp;
				_capacity = n;
			}
		}
		//扩容+初始化
		void resize(size_t n,char ch = '\0')
		{
			if (n <= _size)
			{
				_str[n] = '\0';
			}
			else if(n<=_capacity)
			{
				for (size_t i = _size; i < n; i++)
				{
					_str[i] = ch;
				}
				_str[n] = '\0';
			}
			else if (n > _capacity)
			{
				reserve(n);
				for (size_t i = _size; i < n; i++)
				{
					_str[i] = ch;
				}
				_size = n;
				_str[_size] = '\0';
			}
		}

		//增删查改
		//尾插
		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* str)
		{
			int len = strlen(str);
			if(_size+len>_capacity)
			{
				reserve(_size + len);
			}
			memcpy(_str + _size, str, len + 1);
			_size+=len;
		}
		//operator+=
		string& operator+=(const char ch)
		{
			push_back(ch);
			return *this;
		}
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}
		//插入(默认在pos位置前插入)
		void insert(size_t pos, size_t n, char ch)
		{
			assert(pos <= _size);
			//考虑扩容
			if (_size + n > _capacity)
			{
				reserve(_size + n);
			}
			size_t end = _size;
			//当pos为0时,end减少到0后,再--,end会变成最大值(无符号整形)
			//npos是静态成员变量,npos是最大值
			while (end >= pos && end != npos)
			{
				_str[end + n] = _str[end];
				--end;
			}
			for (size_t i = 0; i < n; i++)
			{
				_str[pos+i] = ch;
			}
			_size += n;
		}
		void insert(size_t pos ,const char* str)
		{
			assert(pos <= _size);
			int len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			size_t end = _size;
			while (end >= pos && end != npos)
			{
				_str[end + len] = _str[end];
				--end;
			}
			memcpy(_str + pos, str, len);
		}
		//删除
		//如果len为npos,直接从pos删至结尾
		void erase(size_t pos, size_t len = npos)
		{
			assert(pos<_size);
			if (pos + len >= _size)
			{
				_size = pos;
				_str[_size] = '\0';
			}
			else
			{
				for (size_t i = 0 ; i <= _size-(pos+len); i++)
				{
					_str[pos+i] = _str[pos+len+i];
				}
				
			}
		}
		//查找
		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);
			//strstr在_str中寻找str,找到就返回str在_str中首字符的地址
			const char* cc = strstr(_str, str);
			if (cc)
			{
				return cc - _str;
			}
			else
			{
				return npos;
			}
		}
		//取子串
		string substr(size_t pos = 0, size_t len = npos)
		{
			assert(pos < _size);

			size_t n = len;
			if (len == npos || pos + len > _size)
			{
				n = _size - pos;
			}

			string tmp;
			tmp.reserve(n);
			for (size_t i = pos; i < pos + n; i++)
			{
				tmp += _str[i];
			}

			return tmp;
		}
		//清理
		void clear()
		{
			//不同于析构函数
			//只清理数据
			_str[0] = '\0';
			_size = 0;
		}
		//比较大小
		bool operator<(const string& str)
		{
			//法1
			size_t i1 = 0, i2 = 0;
			while (i1 < _size && i2 < str._size)
			{
				if (_str[i1] < str._str[i2])
				{
					return true;
				}
				else if (_str[i1] > str._str[i2])
				{
					return false;
				}
				else
				{
					++i1;
					++i2;
				}
			}
			if (i1 == _size && i2 != str._size)
			{
				return true;
			}
			else
			{
				return false;
			}
			//法2
			//int ret = memcmp(_str, str._str, _size < str._size ? _size : str._size);
			//return ret == 0 ? _size < str._size : ret < 0;
		}
		bool operator==(const string& str)
		{
			return _size == str._size && memcmp(_str, str._str, _size) == 0;
		}
		bool operator!=(const string& str)
		{
			return !(*this == str);
		}
		bool operator<=(const string& str)
		{
			return (*this < str) || (*this == str);
		}
		bool operator>(const string& str)
		{
			return !(*this <= str);
		}
		bool operator>=(const string& str)
		{
			return !(*this <= str);
		}
		//operator<<和operator>>
		friend ostream& operator<<(ostream& out, const string& s);
		friend istream& operator>>(istream in, string& s);
	};
	
	// ---------------------------------------------------

	//静态成员变量
	size_t string::npos = -1;
	//友元函数
	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto e : s)
		{
			out << e;
		}
		return out;
	}
	//cout<<s与cout<<s.c_str()有什么区别?
	//c_str返回const char*,识别到'\0'就停止打印。
	//例子
	// string s("hello");s+="world";s+='\0';s+="xxxxx";
	// cout<<s.c_str()<<endl;cout<<s<<endl;
	// 总结:c的字符数组,以'\0'为终止算长度,string不看'\0',以size为终止算长度
	istream& operator>>(istream& in, string& s)
	{
		//注意:>>不会读取空格,但istream的get()每次读取一个字符,可以解决问题
		s.clear();
		//清理缓冲区的空格和换行
		char ch = in.get();
		while (ch == ' ' || ch == '\n')
		{
			ch = in.get();
		}
		//将读取的字符放到数组,可以防止频繁尾插
		char buff[128];
		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)
		{
			s += buff;
		}
		return in;
	}
}

4. 写时拷贝

浅拷贝是指多个对象共享同一份资源,但这就回引发两个问题:(1)多次析构。如何对象管理资源,最终调用析构函数,会导致多次析构;(2)一个对象修改数据,会影响另一个对象的数据。
这时就有深拷贝,深拷贝是指每个对象都有一份属于自己的资源。

在这里插入图片描述
但是如果不同对象对独立的资源只读不写,深拷贝有点浪费资源。所以有更好的方法:写时拷贝。写时拷贝是在浅拷贝的基础上引入了引用计数的概念来实现的。引用计数表示有多少个对象指向同一片空间。当要释放空间时,考虑引用计数是否为1,不为1就不释放空间,为1就释放空间。
在这里插入图片描述
以上可以用来应付拷贝但不修改的情况,防止浪费空间。但如果要修改数据怎么办?修改的时候如果引用计数不为1,则进行深拷贝,再修改数据。在这里插入图片描述


5. 练习

  1. LeetCode917:反转字符串
//思路:从左右两边同时开始进行交换
class Solution {
public:
    string reverseOnlyLetters(string s) {
        int left = 0;
        int right = s.size()-1;
        while(left<right)
        {
            if(isalpha(s[left])&&isalpha(s[right]))
            {
                swap(s[left],s[right]);
                ++left;
                --right;
            }
            else if(isalpha(s[left]))
            {
                --right;
            }
            else if(isalpha(s[right]))
            {
                ++left;
            }
            else
            {
                ++left;
                --right;
            }
        }
        return s;
    }
};
  1. LeetCode415:字符串相加
//思路:从每个数的最后一位开始相加,取其个位尾插到要返回的string对象中,
//     取其十位数作为进位,最后逆置string对象返回。
class Solution {
public:
    string addStrings(string num1, string num2) {
        int end1 = num1.size() - 1;
        int end2 = num2.size() - 1;
        string ret;
        //用来记录进位
        int carry = 0;
        //从最低位开始+
        while(end1>=0 || end2>=0)
        {
            int val1 = end1>=0?num1[end1] - '0':0;
            int val2 = end2>=0?num2[end2] - '0':0;
            int Add = val1 + val2 + carry;
            carry = Add / 10;
            ret += Add % 10 + '0';
            --end1;
            --end2;
        }
        if(carry!=0)
        {
            ret += carry+'0';
        }
        reverse(ret.begin(),ret.end());
        return ret;
    }
};
  1. LeetCode125:验证回文串
class Solution {
public:
    bool isPalindrome(string s) {
        //思路:遍历一遍,将大写字符转换成小写的,
        //将非字母数字字符移除,再判断是否是回文
        for(int i = 0 ; i < s.size();i++ )
        {
            if(isupper(s[i]))
            {
                //如果是大写字母就转换成小写
                s[i] = tolower(s[i]);
            }
            else if(!isalnum(s[i]))
            {
                //如果不是字母或者数字,就移除
                s.erase(i,1);
                i--;
            }
        }
        //判断是否是回文
        int begin = 0;
        int end = s.size()-1;
        while(begin<end)
        {
            if(s[begin]!=s[end])
            {
                return false;
            }
            ++begin;
            --end;
        }
        return true;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值