string
- 1. 简单了解string
- 2. string的常用接口
- 3. 简单模拟实现string
- 4. 写时拷贝
- 5. 练习
1. 简单了解string
- string是表示字符串的字符类,其底层是basic_string模板类的实例化,只能存储单字节的字符串,不能操作多字节和变长字符的序列。
- 在使用string类时,必须包含头文件< string >以及展开命名空间using namespace std。
2. string的常用接口
- 构造函数
a. 用法
(1)默认构造,构造空字符串。(2)拷贝构造。(3)从str中取出子串放入string中。(4)用字符串来构造string的对象。(5)拷贝前n个字符到string对象。(6)将n个相同的字符填充到string对象中。(7)利用迭代器(类似于指针)将该范围内的字符拷贝到string的对象中。
注意
第3点len的缺省值是npos。npos是静态成员变量,表示整形的最大值。当你不传len时,默认从pos位置到字符串结束。
b. 例子
这里只讲四种常见的(用长方形框出来的)。
- 赋值
例子
3. 访问
例子
例子
- 迭代器
先讲前两种
例子
注意
iterator是像指针一样的类型,有可能是指针,也有可能不是指针。但底层都是指针实现。string底层就是指针。
对比
相比operator[],迭代器好像更加复杂。但是言之过早,以下有更方便的范围for。
意义
(1)范围for不比operator[]方便?但这又和迭代器有什么关系?
通过查看范围for的反汇编代码,发现底层是迭代器。
范围for底层就是迭代器,一个类支持迭代器,就支持范围for。
(2)任何容器都支持迭代器,且用法是类似的。
(3)算法可以配合迭代器处理容器内数据。
- 反向迭代器
例子
- const修饰的迭代器
返回由const修饰的迭代器,只能读不能写。
例子
- 容量
(1)size()和length()返回字符串的长度(字节个数)。
(2)capacity()返回字符串所占空间的大小。
(3)clear()清理字符串的内容。
(4)empty()判断字符串是否是空串,是就返回真,不是就返回假。
(5)reserve:预定,保留(不同于reverse:逆置。)
(6)resize
缩容是有代价的,string的缩容很保守(就像free和delete不会单独释放空间的一部分),实际上的缩容是先开一片空间,按规定大小的空间拷贝过去,再释放旧空间,是以时间换空间的。
总结
想开空间同时初始化,用resize(),想先把空间开好,用reserve()。
(7)shrink_to_fit:收缩至合适
但是没必要缩容,以时间换空间,因为现代计算机的内存足够大。
- 追加
append与+=都有追加的作用,但是+=更实用,所以只讲+=。
例子
- 尾插和插入
例子
- 删除
注意
(1)若len不传参,则用缺省值,将字符串从pos位置删除至结尾。
(2)迭代器区间一般都是左闭右开的,如[first,last)。
例子
- c_str得到字符数组的指针
注意
得到的字符数组是该string对象的字符串。
例子
- 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;
}
运行结果
- 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。
-
getline
-
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. 练习
- 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;
}
};
- 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;
}
};
- 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;
}
};