文章目录
1.内容回顾
string是C++标准库里的东西,严格来说不属于STL。所以说大家会看到STL里面,它把一些数据结构划分在Containers,也就是容器里面。但是string没有在这个地方,string在Miscellaneous headers里。STL是C++标准库的一部分,它是关于算法和数据结构的库。
string类是basic_string类模板的一个实例化,它使用char(即bytes)作为其字符类型。basic_string类模板还可以用wstring、u16string、u32string 作为其字符类型,能很好解决不同编码的问题。
basic_string类模板大概原型:为了支持增删查改,所以不会写成_str = str,大家结合string的底层来理解。
template<class T>
class basic_string
{
public:
basic_string(const T* str)
{
// 开空间存储字符串,方便增删查改
size_t len = strlen(str);
_str = new T[len + 1];
strcpy(_str, str);
}
//引用也是为了方便修改,减少拷贝
T& operator[](size_t pos)
{
return _str[pos];
}
private:
T* _str;
size_t _size;
size_t _capacity;
};
如果string对象是const限定的,则该函数返回一个const T&
const T& operator[] (size_t pos) const;
string的构造:
void Teststring()
{
string s1; // 构造空的string类对象s1
string s2("hello world!"); // 用C格式字符串构造string类对象s2
string s3(s2); // 拷贝构造s3
}
string的遍历方式:
#include <string>
#include <iostream>
using namespace std;
int main()
{
string s("hello world");
//s += "hello";
// 1:下标+[]
//s[12];
for (size_t i = 0; i < s.size(); ++i)
{
s[i] += 1;
cout << s[i] << " ";
}
cout << endl;
// 2: 迭代器 -- 像指针一样的访问数据结构的东西
string::iterator it = s.begin();
while (it != s.end())
{
*it -= 1;
cout << *it << " ";
++it;
}
cout << endl;
// 3:范围for--语法糖 底层:编译器替换成了迭代器
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
return 0;
}
注:C语言对越界的检查是抽查,有时候不一定能检查出来。string类对越界的检查很严格,只要越界程序必然崩溃(实际上是用assert来实现)。
2. operator+=和insert
学过数据结构的同学都知道insert必然是不高效的,因为它在插入数据之前一定会挪动数据。如果空间不够甚至还会扩容。
槽点:string的insert,pos下标支持字符串不支持字符,迭代器支持字符不支持字符串,诶。
void test_string1()
{
string s("hello");
s += ' ';
s += "world";
cout << s << endl;
s.insert(0, 1, 'x');
s.insert(s.begin(), 'y');
cout << s << endl;
s.insert(3, 1, 'x');
s.insert(s.begin()+3, 'y');
cout << s << endl;
s.insert(0, "sort ");
cout << s << endl;
}
3. erase
设计有点冗余,其实大家记住第一种用法就基本够用了。
缺省值:npos
如果第二个参数不给,默认是整型的最大值。如果长度超出剩余字符的长度,就从pos位置开始把剩余字符删完。
void test_string2()
{
string s("hello world");
cout << s << endl;
s.erase(s.begin());
cout << s << endl;
s.erase(s.begin()+3);
cout << s << endl;
s.erase(3, 2);
cout << s << endl;
//s.erase(3);
s.erase(3, 100);
cout << s << endl;
}
4. swap
大家猜一猜下面两种交换方式有什么区别:
void test_string3()
{
string s1("hello world");
string s2("string");
// C++98
s1.swap(s2); // 效率高
//STL库中的函数模板,能支持任意类型的交换
swap(s1, s2); // 效率低
}
5. c_str
返回一个指向数组的指针,该数组包含一个以空结束的字符序列(即C-string),表示string对象的当前值, 能很好的和C语言(如strcpy)的一些接口兼容配合。
void test_string4()
{
string s1("hello world");
cout << s1 << endl;
cout << s1.c_str() << endl;
}
6. find、rfind、substr
返回第一次匹配的第一个字符的位置。如果没有找到匹配,函数返回string::npos。
返回最后一次匹配的第一个字符的位置。 如果没有找到匹配,函数返回string::npos。
返回具有此对象的子字符串的字符串对象(从pos位置返回len长度的子字符串)。
6.1 取出文件后缀
- 只有 一个后缀:
void test_string5()
{
// 要求取出文件的后缀
string file("string.cpp");
//string file("string.c");
size_t pos = file.find('.');
if (pos != string::npos)
{
string suffix = file.substr(pos, file.size() - pos);
//string suffix = file.substr(pos);
cout << file << "后缀:" << suffix << endl;
}
else
{
cout << "没有后缀" << endl;
}
}
- 多个后缀:倒着找
void test_string6()
{
// 要求取出文件的后缀
string file("string.c.tar.zip");
size_t pos = file.rfind('.');
if (pos != string::npos)
{
//string suffix = file.substr(pos, file.size() - pos);
string suffix = file.substr(pos);
cout << file << "后缀:" << suffix << endl;
}
else
{
cout << "没有后缀" << endl;
}
}
6.2 分离协议、域名和URI
void test_string7()
{
// 取出url中的域名
string url1("http://www.cplusplus.com/reference/string/string/find/");
string url2("https://leetcode.cn/problems/design-skiplist/solution/tiao-biao-probabilistic-alternative-to-b-0cd8/");
string& url = url1;
// 协议 域名 uri
string protocol;
size_t pos1 = url.find("://");
if (pos1 != string::npos)
{
//pos下标(:)就是协议字符的个数
protocol = url.substr(0, pos1);
cout << "protocol:" << protocol << endl;
}
else
{
cout << "非法url" << endl;
}
string domain;
//第二个参数表示从":"位置+3开始找
size_t pos2 = url.find('/', pos1+3);
if (pos2 != string::npos)
{
domain = url.substr(pos1+3, pos2 - (pos1+3));
cout << "domain:" << domain << endl;
}
else
{
cout << "非法url" << endl;
}
string uri = url.substr(pos2 + 1);
cout << "uri:" << uri << endl;
}
string& url = url1;
string& url = url2;
7. 关系运算符重载
其实没必要提供这么多,接口。就比如operator<
如果不提供后面两个,我们也可以用构造函数来进行比较。
关系运算符不能用函数模板,因为每一个类的比较规则都不一样。
swap可以用函数模板是因为不管什么类型都是借助第三个变量来实现交换。
8. getline
字符串最后一个单词的长度
C语言的scanf和C++的cin都是默认多个数据以空格或者\n间隔。如果缓冲区有以空格分割的多个数据,cin和scanf是取不完的,碰到第一个空格就结束了。此时getline就能很好地解决这个问题。
#include <iostream>
#include <string>
using namespace std;
int main()
{
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;
}
9. 练习题
9.1 字符串中的第一个唯一字符
字符串中的第一个唯一字符
利用哈希的思想,因为只包含小写字母,所以我们开一个26大小的数组。
class Solution {
public:
int firstUniqChar(string s) {
int count[26] = {0};
//统计每个字符出现的次数
for(auto ch : s)
{
count[ch-'a']++;
}
for(size_t i = 0; i < s.size(); ++i)
{
if(count[s[i]-'a'] == 1)
return i;
}
return -1;
}
};
9.2 字符串相加
字符串相加
大数运算,注意考虑进位,进位初始化为0.如果大于9,进位就为1,每次进位后记得重置为0.最后把运算结果头插。由于string的insert效率极低(持续的头插是n^2),考虑尾插后再逆置(O(n))。如果两个个位数相加的结果大于9,while循环结束后还得再加上进位的1.
注:字符0的ASCII是48,字符数字要与’0’相减换算成对应的值。
class Solution {
public:
string addStrings(string num1, string num2) {
int end1 = num1.size()-1;
int end2 = num2.size()-1;
int carry = 0;
string retstr;
while(end1 >= 0 || end2 >= 0)
{
int val1 = end1 >= 0 ? num1[end1]-'0' : 0;
int val2 = end2 >= 0 ? num2[end2]-'0' : 0;
int ret = val1 + val2 + carry;
if(ret > 9)
{
ret -= 10;
carry = 1;
}
else
{
carry = 0;
}
retstr += ('0' + ret);
--end1;
--end2;
}
if(carry == 1)
retstr += '1';
reverse(retstr.begin(), retstr.end());
return retstr;
}
};