string是STL中的类,相比于C语言的字符数组和字符指针,string对字符串的处理方便很多。
一、字符的一些补充
众所周知英文字符等符号在计算机中采用ASCII码的方式储存。 但是对于中文等其他字符,ASCII码则无法表示(因为一个字节存不下),对于这些字符,则采用Unicode编码么标准:它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。当然Unicode标准下字符是两个字节,这样就能存下大部分字符了。 当然也有新的字符型wchar_t 来存储两个字节的字符。
二、string类
相比于C语言,string类型更符合面向对象的思想,它将string以及成员函数放在了一个头文件<string> 中,而库< iostream > 已经隐式地包含了<string> 头文件,所以无需显式引用该头文件也能够使用string类。这样更方便一些。
相比于C语言的字符数组或字符指针,string更安全一些,字符数组或字符指针很容易就会越界访问。
了解string类最好的方式就是查看官方文档。 string类
2.1.string类的常见构造函数和析构函数
在C++98中有以下七种构造函数:
其中比较重要的是默认构造、字符串构造和拷贝构造。
#include<iostream>
using namespace std;
int main()
{
string s1;
string s2("hello");
string s3(s2);
string s4("hello", 1);
string s5("hello", 1, 2);
string s6("hello", 1, string::npos);
string s7("hello", 1, 20);
string s8(5, 'a');
string s9 = "hello";
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
cout << s5 << endl;
cout << s6 << endl;
cout << s7 << endl;
cout << s8 << endl;
cout << s9 << endl;
s1 = s8;
cout << s1 << endl;
return 0;
}
析构函数则是在对象销毁时自动调用,销毁开辟的空间:
2.2.三种遍历方式
- 采用下标的方式
size()是一个公共成员函数,返回string的长度。和length()相同。
- 迭代器遍历
迭代器iterator是string中的类,所以要加域作用限定符。
begin()返回开始的下标位置的迭代器。 迭代器返回的是一个左闭右开的下标区间,所以end()返回的不是最后一个数据位置的迭代器,返回是最后一个位置下一个位置。 C++中凡是给迭代器一般都是给的左闭右开的区间。迭代器是类似指针一样的东西,它的用法就是模拟指针。
迭代器的意义是遍历更复杂的容器,比如list、map等。
反向迭代器:
反向迭代器++的时候是倒着往前走的。
const对象只能使用const迭代器遍历,const迭代器是只读:
iterator遍历普通对象是可读可写的,const_iterator 遍历const对象是只读的。
- 范围for
依次取容器中的数据赋值给e,自动判断结束。 范围for实现的本质是迭代器,因此支持其他容器的遍历。
2.3.string常用的接口
几个常用的函数:
- push_back(ch)
尾插一个字符ch - append(str)
在字符串后追加一个字符串str - +=str
在字符串后追加一个字符串str。重载了+=.。相比于append,实际中更常用+= - insert(pos,str) insert(pos,n,ch)
在下标为pos位置插入一个字符串str。或者插入n个字符ch。尽量少用insert,因为插入需要挪动数据。 - erase(pos,len)
在下标为pos后删除长度为len的字符,如果不写pos和len则默认从开始的位置向后全删除。 - resize(n,ch)
改变size的大小到n,capacity也会改变。多出来的size空间则用字符ch补全,如果不写ch则默认为\0 。 - reserve(n)
改变容量到大于n的大小,size并不会改变。 - c_str()
获取对象中字符串的首地址。 - find(ch/str,pos)和rfind(ch/str,pos)
从pos位置开始找一个字符或者字符串,pos不写默认给0。返回字符或字符串在源字符串的下标位置。 rfind从后往前找。 - substr(pos,len)
返回字符串从pos位置开始,长度为len的部分。
通过find和substr两个函数可以查找网址的域名和协议:
#include<iostream>
using namespace std;
string GetDomain(const string& url)
{
size_t pos = url.find("://");
if (pos != string::npos)
{
size_t start = pos + 3;
size_t end = url.find('/', start);
if (end != string::npos)
{
return url.substr(start, end - start);
}
else
{
return string();
}
}
else
{
return string();
}
}
string GetProtocol(const string& url)
{
size_t pos = url.find("://");
if (pos != string::npos)
{
return url.substr(0, pos - 0);
}
else
{
return string();
}
}
int main()
{
string url = "https://blog.csdn.net/qq_44918090/article/details/118532221";
cout << GetDomain(url) << endl;
cout << GetProtocol(url) << endl;
return 0;
}
- getline(cin,s)
往string对象s中输入一行(包括空格),遇到换行终止。 - reverse(s.begin(),s.end())
逆置s,传入开始和结束的迭代器。
三、string模拟实现
string本质上是一个管理字符串的顺序表(数组),字符串的结尾有\0 ,所以采用顺序表的写法即可。
3.1.深浅拷贝的问题
string类模拟实现需要注意的问题就是深浅拷贝,所以拷贝构造函数应该采用深拷贝的方式,因为新的对象也需要开辟空间,如果采用浅拷贝就会导致它们指向同一块空间,在析构时会释放两次该空间。
如果是深拷贝:
另外,赋值运算符= 默认也是浅拷贝。
3.2.命名空间
如果不加命名空间和库实现隔离,则会和库里面的string重名导致无法调用,当然也可以给模拟实现的string改类名,这里采用命名空间的解决办法,命名空间起名为A,当然也可以起别的名字:
namespace A
{
class string
{
private:
char* _str;
size_t _size;
size_t _capacity;
static const size_t npos;
};
const size_t string::npos = -1;
}
3.3.默认成员函数
构造函数
string(const char* str = "")
::_str(new char[strlen(str) + 1])
, _size(strlen(str))
, _capacity(_size + 1)
{
strcpy(_str, str);
}
string(const string& s)
:_str(new char[strlen(s._str) + 1])
,_size(strlen(s._str))
,_capacity(_size+1)
{
strcpy(_str, s._str);
}
析构函数
~string()
{
delete[]_str;
_str = nullptr;
}
重载赋值运算符=
string& operator=(string s)
{
swap(s);
return *this;
}
3.4.元素访问函数
[]重载
char& operator[](size_t i)
{
assert(i < _size);
return _str[i];
}
const char& operator[](size_t i)const
{
assert(i < _size);
return _str[i];
}
find和rfind
size_t find(char ch, size_t pos = 0)const
{
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)const
{
assert(pos < _size);
const char* ret = strstr(_str+pos, str);
if (ret)
{
return ret - _str;
}
else
{
return npos;
}
}
size_t rfind(char ch, size_t pos = npos)
{
string tmp(*this);
reverse(tmp.begin(), tmp.end());
if (pos >= _size)
{
pos = _size - 1;
}
pos = _size - 1 - pos;
size_t ret = tmp.find(ch, pos);
if (ret != npos)
{
return _size - 1 - ret;
}
else
{
return npos;
}
}
size_t rfind(const char* str, size_t pos = npos)
{
string tmp(*this);
size_t len = strlen(str);
char* arr = new char[len + 1];
strcpy(arr, str);
int left = 0, right = len - 1;
while (left < right)
{
::swap(arr[left], arr[right]);
left++;
right--;
}
reverse(tmp.begin(), tmp.end());
if (pos >= _size)
{
pos = _size - 1;
}
pos = _size - 1 - pos;
size_t ret = tmp.find(arr, pos);
delete[] arr;
if (ret != npos)
{
return _size - ret - len;
}
else
{
return npos;
}
}
c_str
char* c_str()const
{
return _str;
}
3.5.容积函数
size()和capacity()
size_t size()const
{
return strlen(_str);
}
size_t capacity()
{
return _capacity;
}
reserve
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n+1];
strncpy(tmp, _str,_size+1);
delete[]_str;
_str = tmp;
_capacity = n+1;
}
}
resize
void resize(size_t n, char val = '\0')
{
if (n < _size)
{
_size = n;
_str[_size] = '\0';
}
else
{
if (n > _capacity)
{
reserve(n);
}
memset(_str + _size, val, n - _size);
_size = n;
_str[n] = '\0';
}
}
empty
bool empty()
{
return strcmp(_str, "") == 0;
}
clear
void clear()
{
_size = 0;
_str[0] = '\0';
}
3.6.增删查改
用来交换对象中成员变量的swap函数
void swap(string& s)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
这个函数的目的是为了交换对象中的成员变量,在接下来其他函数的实现中被调用。
insert
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size +len > _capacity)
{
reserve(_size + len);
}
char* end = _str + _size;
while (end >= _str + pos)
{
*(end + len) = *end;
end--;
}
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
string& insert(size_t pos, size_t n, char ch)
{
assert(pos <= _size);
if ((_size+n) >= _capacity)
{
reserve(_capacity == 0 ? (_size + n) : (_size + n) * 2);
}
char* end = _str+_size;
while (end >= _str + pos)
{
*(end + n) = *end;
end--;
}
memset(_str + pos, ch, n);
_size+=n;
return *this;
}
push_back和append
void push_back(char ch)
{
insert(_size,1, ch);
}
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;
}
erase
string& erase(size_t pos,size_t len=npos)
{
assert(pos < _size);
size_t leftlen = _size - pos;
if (len >= leftlen)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
3.7.迭代器
begin()和end()迭代器
iterator begin()
{
return _str;
}
const_iterator begin()const
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator end()const
{
return _str + _size;
}
3.8.非成员函数重载
重载输出<<
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;
ch=in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
getline
istream& getline(istream& in, string& s)
{
s.clear();
char ch;
ch = in.get();
while (ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
3.9.关系运算符重载
参考日期类的关系运算符重载,>、>=、<、<=、==、!= 只需要实现出两个,其他直接复用这两个即可。
bool operator<(const string& s1, const string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) < 0;
}
bool operator==(const string& s1, const string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator<=(const string& s1, const string& s2)
{
return s1 < s2 || s1 == s2;
}
bool operator>(const string& s1, const string& s2)
{
return !(s1 <= s2);
}
bool operator>=(const string& s1, const string& s2)
{
return !(s1 < s2);
}
bool operator!=(const string& s1, const string& s2)
{
return !(s1 == s2);
}
|