string类是文本字符串的标准库类,定义在C++标准库
,用来管理字符串,它提供了很多方法来处理字符串。
默认构造string()
string s1;
拷贝构造string (const string& str)
string s1 = "hello world";
string s2(s1);
cout<< s1 << endl; //hello world
cout<< s2 << endl; //hello world
构造字符串中的字串string (const string& str, size_t pos, size_t len = npos)
npos
,是size_t
的最大值
string s1 = "hello world";
string s2(s1, 6); //从下标为6的位置开始拷贝构造,拷贝到\0截至 world
string s3(s1, 0, 5); //从下标为0的位置开始拷贝构造,长度为5 hello
构造n个连续的字符string (size_t n, char c)
string s(5, 'x'); //xxxxx
完整的可以参考文档string构造函数、string析构函数、string赋值运算符重载
定义出string
对象之后,想要访问里面的内容,C++重载了operator[],让我们可以像访问数组一样去访问这个对象。
//std::string::operator[]
char& operator[] (size_t pos);
const char& operator[] (size_t pos) const;
void test4()
{
string s("hello world");
//遍历
for (size_t i = 0; i <s.size(); i++)
{
cout << s[i] << ' ';
}
cout << endl;
//修改
for (size_t i = 0; i < s.size(); i++)
{
s[i] = 'x';
cout << s[i] << ' ';
}
}
string
还支持at
、back
、front
这几种访问方式。
除了用下标+[],C++中还引入了迭代器的概念:
void test5()
{
string s("hello world");
string::iterator it = s.begin();
while (it != s.end())
{
cout << *it << ' ';
++it;
}
}
这种访问方式就类似与指针。
我们所使用的范围for的底层,其实就是迭代器
void test5()
{
string s("hello world");
string::iterator it = s.begin();
while (it != s.end())
{
cout << *it << ' ';
++it;
}
cout << endl;
for (auto ch : s)
{
cout << ch << endl;
}
}
在STL中,任何容器都是支持迭代器的,迭代器也可以跟算法配合。
void test6()
{
string s("hello world");
cout << s << endl;
//与算法配合
reverse(s.begin(),s.end());
for (auto ch : s)
{
cout << ch;
}
cout<<endl;
}
迭代器有四种迭代方式:
size_t size() const; //size
size_t length() const; //length
查看文档发现,size
和length
两个的定义貌似是一样的
void test6()
{
string s("hello");
cout << s.size() << endl; //5
cout << s.length() << endl; //5
}
而且我们调用这两个方法,输出的也是一样的。我猜测早期在设置string
的时候,是为了处理字符串,只需要求字符串的长度即可。
在后来STL问世之后,例如二叉树什么的,这时候length
就很不合适了,所以统一用size
,然后string
要向前兼容保留了lenght
,同时也增加了size
。
size_t max_size() const;
**max_size
**的意思就是看这个字符串能达到的最大长度,这个在不同编译器或者不同环节下,可能有所不同。
size_t capacity() const;
不仅仅是对与max_size
,容量在不同平台给的也不一样
VS2022,会内存对齐,这上面显式的是15,其实真正的是16,还给
\0
留了一个空间而g++,初识容量是多少就给多少,每次二倍扩容
void reserve (size_t n = 0);
多次可能会产生过多的内存碎片,空间利用率也不是很高,reserve
接口就可以向内存申请空间,为字符串预留一段指定的空间。
void test9()
{
string s = "hello world";
cout << s.capacity() << endl;
s.reserve(100);
cout << s.capacity() << endl;
}
当然了这在不同环境下,也是不同的:
如果先向内存申请了100个字节的空间,之后又改为了50,这虽然符合语法,但具体还是由编译器实现
string s = "hello world"; s.reserve(100); cout << s.capacity() << endl; //VS2022:111 g++:100 s.reserve(50); cout << s.capacity() << endl; //VS2022:111 g++:50
void clear();
clear
可以将string
的字符全部删除,让其变成一个空字符串
void test10()
{
string s("hello");
cout << s << endl; // hello
cout << s.size() << endl; // 5
s.clear();
cout << s << endl; //
cout << s.size() << endl; // 0
}
Tips:
当用clear清理之后,如果reserve申请的空间比初始空间小,则会缩容
void test() { string s = "hello world"; cout << s.capacity() << endl; // 15 s.reserve(100); cout << s.capacity() << endl;// 111 s.clear(); s.reserve(10); cout << s.capacity() << endl; // 15 }
void resize (size_t n);
void resize (size_t n, char c);
reserve
就有点类似于C语言在的malloc
,就是单纯的开空间;而resize
就有点realloc
的意思。
这里重载了2个函数,如果传入字符,就按照传入的字符填值,如果不传则默认填入\0
。
如果传入的n比当前大小要小,只会保留n之前的字符,但不会缩容。
void test11()
{
string s1 = "hello";
string s2 = "hello";
string s3 = "hello world";
s1.resize(10);
s2.resize(10, 'x');
s3.resize(5);
}
bool empty() const;
返回值为bool
类型,判断是否为一个空字符串
void shrink_to_fit();
将字符串减小到合适的大小,当然这个请求不具约束力
string& operator+= (const string& str);
string & operator+= (const char* s);
string& operator+= (char c);
重载+=
操作符,在字符串的末尾加上要添加的字符,也理解为将添加的字符串尾插到原字符串
void test1()
{
string s = "abandon"; // abandon
cout << s << endl;
s += " world";
cout << s << endl; // abandon world
}
string& append(const string& str);
string& append(const string& str, size_t subpos, size_t sublen);
string & append(const char* s);
string& append(const char* s, size_t n);
string& append(size_t n, char c);
template <class InputIterator>
string& append(InputIterator first, InputIterator last);
也是将字符串尾插到原字符串中,只不过这里有可以插入多少的选项,不过感觉这还是有点鸡肋
void test2()
{
string s = "abandon";
cout << s << endl; // abandon
s.append(" world");
cout << s << endl; // abandon world
s.append(" world", 2);
cout << s << endl; // abandon world w
s.append(" world", 0, 2);
cout << s << endl; // abandon world w w
}
void push_back (char c);
这就是正儿八经的尾插了,如果学习过数据结构,应该一看这个名字就知道是尾插的接口,每次插入一个字符。
void test3()
{
string s = "1234";
s.push_back('5');
s.push_back('6');
s.push_back('7');
cout << s << endl; // 1234567
}
string& assign(const string& str);
string& assign(const string& str, size_t subpos, size_t sublen);
string & assign(const char* s);
string& assign(const char* s, size_t n);
string& assign(size_t n, char c);
template <class InputIterator>
string& assign(InputIterator first, InputIterator last);
将原字符串的内容替换掉,这几个接口都大同小异,用法都差不多
void test4()
{
string s = "1234";
cout << s << endl; // 1234
s.assign("567");
cout << s << endl; // 567
}
string& insert(size_t pos, const string& str);
string& insert(size_t pos, const string& str, size_t subpos, size_t sublen);
string & insert(size_t pos, const char* s);
string& insert(size_t pos, const char* s, size_t n);
string& insert(size_t pos, size_t n, char c);
void insert(iterator p, size_t n, char c);
iterator insert(iterator p, char c);
template <class InputIterator>
void insert(iterator p, InputIterator first, InputIterator last);
指定位置插入操作
void test5()
{
string s1 = "1234";
cout << s1 << endl; // 1234
s1.insert(0, "5678");
s1.insert(s1.size(), 3, '9');
cout << s1 << endl; // 56781234999
string s2 = "hello";
cout << s2 << endl;
s2.insert(s2.end(), 5, 'a');
cout << s2 << endl; //helloaaaaa
}
string& erase(size_t pos = 0, size_t len = npos);
iterator erase(iterator p);
iterator erase(iterator first, iterator last);
上面这些操作可以理解为插入操作,erase
就是指定位置删除操作了。这里参数给了缺省值,如果我们没有指定删除多少,则会从指定位置往后全部删完。
void test6()
{
string s = "12345";
s.erase(0, 1);
cout << s << endl; //2345
s.erase(1);
cout << s << endl; //2
}
这个结构太复杂了,感兴趣可点击查看文档replace
replace
将指定的字符从指定位置替换,可选择替换掉多少个字符。
void test7()
{
string s = "1234";
s.replace(1, 2, "abc");
cout << s << endl; // 1abc4
}
void pop_back();
尾删操作
void test8()
{
string s = "12345";
s.pop_back();
cout << s << endl; // 1234
s.pop_back();
cout << s << endl; // 123
}
const char* c_str() const;
这是为了兼容C语言,给C提供的一个接口
void test10()
{
string s = "hello";
char* cstr = new char[s.size() + 1];
strcpy(cstr, s.c_str());
cout << cstr << endl; // hello
}
size_t find(const string& str, size_t pos = 0) const;
size_t find(const char* s, size_t pos = 0) const;
size_t find(const char* s, size_t pos, size_t n) const;
size_t find(char c, size_t pos = 0) const;
查找字符/字符串是否出现现在原字符串。
这些这就也都不是很常用,感兴趣可以查阅文档
string substr (size_t pos = 0, size_t len = npos) const;
用于获取子字符串,用法示例:
void test9()
{
string url = "https://blog.csdn.net/Dirty_artist?spm=1019.2139.3001.5343";
size_t pos1 = url.find("://");
string protocol;
string domain;
string uri;
if(pos1!=string::npos)
protocol = url.substr(0, pos1);
size_t pos2 = url.find('/', pos1 + 3);
if (pos2 != string::npos)
domain = url.substr(pos1 + 3, pos2 - (pos1 + 3));
uri = url.substr(pos2 + 1);
cout << protocol << endl;
cout << domain << endl;
cout << uri << endl;
}
这个设计有点冗余,我们可以直接使用重载操作符,有兴趣课看文档compare
文档:relational operators (string)
string的设计稍微有些冗余,不需要全部都记住,需要用的时候查阅文档就行。
那本期分享就到这里咯,我们下期再见,如果还有下期的话。