文章目录
- 目录
- 1. STL简介
- 1.1 什么是STL
- 1.2 STL的版本
- 1.3 STL的六大组件
- 2. 为什么学习string类
- 3. 标准库中的string类
- 3.1 string类
- 3.2 string类的常用接口说明
目录
- STL简介
- 为什么学习string类
- 标准库中的string类
- string类的模拟实现
- 现代版写法的String类
- 写时拷贝
1. STL简介
1.1 什么是STL
STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。
1.2 STL的版本
- 原始版本
Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使用。HP 版本–所有STL实现版本的始祖。
- P. J. 版本
由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低,符号命名比较怪异。
- RW版本
由Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般。
- SGI版本
由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版本。被GCC(Linux)采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程风格上看,阅读性非常高。我们后面学习STL要阅读部分源代码,主要参考的就是这个版本。
1.3 STL的六大组件
2. 为什么学习string类
C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
3. 标准库中的string类
3.1 string类
string类的具体信息可以通过cplusplus网站进行查阅
在使用string类时,必须包含#include头文件以及using namespace std;
3.2 string类的常用接口说明
我们先来总的看一下string类的常用接口:
- string类对象的常见构造
- string类对象的容量操作
- string类对象的访问及遍历操作
- string类对象的修改操作
- string类非成员函数
接下来,我们通过一些具体的场景来学习如何使用这些接口:
- 如何构造一个string类对象
#include <iostream>
#include <string>
using namespace std;
void test_string1()
{
//常用
string s1;
string s2("hello world");
string s3(s2);
//不常用 了解
string s4(s2, 3, 5);
string s5(s2, 3);
string s6(s2, 3, 30);
string s7("hello world", 5);
string s8(10, 'x');
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
cout << s5 << endl;
cout << s6 << endl;
cout << s7 << endl;
cout << s8 << endl;
cin >> s1;
cout << s1 << endl;
}
int main()
{
test_string1();
return 0;
}
补充:
void push_back(const string& s)
{
}
void test_string2()
{
//构造
string s1("hello world");
//隐式类型转换
string s2 = "hello world";
const string& s3 = "hello world";
push_back(s1);
push_back("hello world");
}
int main()
{
test_string2();
return 0;
}
- string的遍历
第一种方法:
//class string
//{
//public:
// //引用返回
// //1. 减少拷贝
// //2. 修改返回对象
// char& operator[](size_t i)
// {
// assert(i < _size);
//
// return _str[i];
// }
//private:
// char* _str;
// size_t _size;
// size_t _capacity;
//};
void test_string3()
{
string s1("hello world");
cout << s1.size() << endl;//11
//cout << s1.length() << endl;//11
for (size_t i = 0; i < s1.size(); i++)
{
s1[i]++;
}
s1[0] = 'x';
//越界检查
//s1[20];
for (size_t i = 0; i < s1.size(); i++)
{
//cout << s1.operator[](i) << " ";
cout << s1[i] << " ";
}
cout << endl;
const string s2("hello world");
//不能修改
//s2[0] = 'x';
}
int main()
{
test_string3();
return 0;
}
注: size() 与 length() 方法底层实现原理完全相同,引入 size() 的原因是为了与其他容器的接口保持一致,一般情况下基本都是用 size() 。
第二种方法:
void test_string4()
{
string s1("hello world");
//遍历方式2:迭代器
string::iterator it1 = s1.begin();
while (it1 != s1.end())
{
*it1 += 3;
cout << *it1 << " ";
++it1;
}
cout << endl;
//cout << typeid(it1).name() << endl;
}
int main()
{
test_string4();
return 0;
}
void test_string4()
{
list<int> lt1;
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
list<int>::iterator it = lt1.begin();
while (it != lt1.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
int main()
{
test_string4();
return 0;
}
第三种方法:
void test_string4()
{
string s1("hello world");
//遍历方式3:范围for(通用的)
//底层角度,它就是迭代器
for (auto& e : s1)
{
e++;//不会影响s1中的数据,它是一个赋值拷贝;要加上引用才会改变s1中的数据
cout << e << " ";
}
cout << endl;
cout << s1 << endl;
list<int> lt1;
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
for (auto& e : lt1)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
test_string4();
return 0;
}
注: 除了普通迭代器之外,还有
- const迭代器
void test_string5()
{
const string s1("hello world");
//string::const_iterator it1 = s1.begin();
auto it1 = s1.begin();
while (it1 != s1.end())
{
//不能修改
//*it1 += 3;
cout << *it1 << " ";
++it1;
}
cout << endl;
}
int main()
{
test_string5();
return 0;
}
- 反向迭代器
void test_string5()
{
string s2("hello world");
string::reverse_iterator it2 = s2.rbegin();
//auto it2 = s2.rbegin();
while (it2 != s2.rend())
{
*it2 += 3;
cout << *it2 << " ";
++it2;
}
cout << endl;
const string s3("hello world");
string::const_reverse_iterator it3 = s3.rbegin();
//auto it3 = s3.rbegin();
while (it3 != s3.rend())
{
//不能修改
//*it3 += 3;
cout << *it3 << " ";
++it3;
}
cout << endl;
}
int main()
{
test_string5();
return 0;
}
- 按字典序排序
#include <algorithm>
void test_string6()
{
string s1("hello world");
cout << s1 << endl;
//s1按字典序(ASCII码)排序
//sort(s1.begin(), s1.end());
//第一个和最后一个不参与排序
//sort(++s1.begin(), --s1.end());
//前5个排序
sort(s1.begin(), s1.begin() + 5);
cout << s1 << endl;
}
int main()
{
test_string6();
return 0;
}
- 插入字符
void test_string7()
{
string s1("hello world");
cout << s1 << endl;
s1.push_back('x');
cout << s1 << endl;
s1.append(" yyyyyy!!");
cout << s1 << endl;
string s2("111111");
s1 += 'y';
s1 += "zzzzzzzz";
s1 += s2;
cout << s1 << endl;
}
int main()
{
test_string7();
return 0;
}
注: 在string尾部追加字符时,s.push_back(‘c’) / s.append(1, ‘c’) / s += ‘c’ 三种的实现方式差不多,一般情况下string类的 += 操作用的比较多,+= 操作不仅可以连接单个字符,还可以连接字符串。
- 关于修改的一些接口
void test_string8()
{
string s1("hello world");
cout << s1 << endl;
s1.assign("111111");
cout << s1 << endl;
//慎用,因为效率不高 -> O(N)
//实践中需求也不高
string s2("hello world");
s2.insert(0, "xxxx");
cout << s2 << endl;
s2.insert(0, 1, 'y');
cout << s2 << endl;
s2.insert(s2.begin(), 'y');
cout << s2 << endl;
s2.insert(s2.begin(), s1.begin(), s1.end());
cout << s2 << endl;
}
int main()
{
test_string8();
return 0;
}
void test_string9()
{
string s1("hello world");
cout << s1 << endl;
//erase效率不高,慎用,和insert类似,要挪动数据
s1.erase(0, 1);
cout << s1 << endl;
//s1.erase(5);
s1.erase(5, 100);
cout << s1 << endl;
//replace效率不高,慎用,和insert类似,要挪动数据
string s2("hello world");
s2.replace(5, 1, "%20");
cout << s2 << endl;
string s3("hello world hello bit");
for (size_t i = 0; i < s3.size(); )
{
if (' ' == s3[i])
{
s3.replace(i, 1, "%20");
i += 3;
}
else
{
i++;
}
}
cout << s3 << endl;
string s4("hello world hello bit");
string s5;
for (auto ch : s4)
{
if (ch != ' ')
{
s5 += ch;
}
else
{
s5 += "%20";
}
}
cout << s5 << endl;
}
int main()
{
test_string9();
return 0;
}
我们来做几个题目:
- 仅仅反转字母
class Solution
{
public:
bool isLetter(char ch)
{
if (ch >= 'a' && ch <= 'z')
{
return true;
}
if (ch >= 'A' && ch <= 'Z')
{
return true;
}
return false;
}
string reverseOnlyLetters(string s)
{
if (s.empty())
{
return s;
}
size_t begin = 0, end = s.size() - 1;
while (begin < end)
{
while (begin < end && !isLetter(s[begin]))
{
++begin;
}
while (begin < end && !isLetter(s[end]))
{
--end;
}
swap(s[begin], s[end]);
++begin;
--end;
}
return s;
}
};
- 字符串中的第一个唯一字符
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 (1 == count[s[i] - 'a'])
{
return i;
}
}
return -1;
}
};
- 验证回文串
class Solution
{
public:
bool isLetterOrNumber(char ch)
{
return (ch >= '0' && ch <= '9')
|| (ch >= 'a' && ch <= 'z');
}
bool isPalindrome(string s)
{
for (auto& ch : s)
{
if (ch >= 'A' && ch <= 'Z')
{
ch += 32;
}
}
int begin = 0, end = s.size() - 1;
while (begin < end)
{
while (begin < end && !isLetterOrNumber(s[begin]))
{
++begin;
}
while (begin < end && !isLetterOrNumber(s[end]))
{
--end;
}
if (s[begin] != s[end])
{
return false;
}
else
{
++begin;
--end;
}
}
return true;
}
};
- 字符串相加
法一:
//时间复杂度:O(N^2) 因为头插的效率太低
class Solution
{
public:
string addStrings(string num1, string num2)
{
int end1 = num1.size() - 1;
int end2 = num2.size() - 1;
string str;
int next = 0;//进位
while (end1 >= 0 || end2 >= 0)
{
int x1 = end1 >= 0 ? num1[end1--] - '0': 0;
int x2 = end2 >= 0 ? num2[end2--] - '0': 0;
int x = x1 + x2 + next;
//处理进位
next = x / 10;
x = x % 10;
//头插
//str.insert(0, 1, '0' + x);
str.insert(str.begin(), '0' + x);
}
if (1 == next)
{
str.insert(str.begin(), '1');
}
return str;
}
};
法二:
//时间复杂度:O(N)
class Solution
{
public:
string addStrings(string num1, string num2)
{
int end1 = num1.size() - 1;
int end2 = num2.size() - 1;
string str;
int next = 0;//进位
while (end1 >= 0 || end2 >= 0)
{
int x1 = end1 >= 0 ? num1[end1--] - '0': 0;
int x2 = end2 >= 0 ? num2[end2--] - '0': 0;
int x = x1 + x2 + next;
//处理进位
next = x / 10;
x = x % 10;
//尾插
str += ('0' + x);
}
if (1 == next)
{
str += '1';
}
reverse(str.begin(), str.end());
return str;
}
};
- string类对象的容量操作
void TestPushBack()
{
string s;
size_t sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
cout << "making s grow:\n";
for (int i = 0; i < 200; ++i)
{
s.push_back('c');
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
void test_string10()
{
string s1("hello world hello bit");
cout << s1.size() << endl;
cout << s1.capacity() << endl;
cout << s1.max_size() << endl;
TestPushBack();
string s1("111111111");
string s2("11111111111111111111111111111111111111111111111111");
}
int main()
{
test_string10();
return 0;
}
void TestPushBack()
{
string s;
//知道需要多少空间,提前开好
s.reserve(200);
size_t sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
cout << "making s grow:\n";
for (int i = 0; i < 200; ++i)
{
s.push_back('c');
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
void test_string10()
{
TestPushBack();
string s1("111111111");
string s2("11111111111111111111111111111111111111111111111111");
cout << s1.capacity() << endl;
s1.reserve(100);
cout << s1.capacity() << endl;
s1.reserve(20);
cout << s1.capacity() << endl;
}
int main()
{
test_string10();
return 0;
}
void test_string11()
{
string s1;
//s1.resize(5, '0');
s1.resize(5);
s1[4] = '3';
s1[3] = '4';
s1[2] = '5';
s1[1] = '6';
s1[0] = '7';
//76543
//插入(空间不够会扩容)
string s2("hello world");
s2.resize(20, 'x');
//删除
s2.resize(5);
//s2[10];
try
{
s2.at(10);
}
catch (const exception& e)
{
cout << e.what() << endl;
}
}
int main()
{
test_string11();
return 0;
}
注:
- clear()只是将string中有效字符清空,不改变底层空间大小。(代码中没有演示)
- resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
- reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
- 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。
- string类的一些其他操作
#define _CRT_SECURE_NO_WARNINGS 1
void test_string12()
{
string file("test.cpp");
FILE* fout = fopen(file.c_str(), "r");
char ch = fgetc(fout);
while (ch != EOF)
{
cout << ch;
ch = fgetc(fout);
}
}
int main()
{
test_string12();
return 0;
}
void test_string12()
{
string file("string.cpp.zip");
size_t pos = file.rfind('.');
//string suffix = file.substr(pos, file.size() - pos);
string suffix = file.substr(pos);
cout << suffix << endl;
}
int main()
{
test_string12();
return 0;
}
void test_string12()
{
string url("https://gitee.com/ailiangshilove/cpp-class/blob/master/%E8%AF%BE%E4%BB%B6%E4%BB%A3%E7%A0%81/C++%E8%AF%BE%E4%BB%B6V6/string%E7%9A%84%E6%8E%A5%E5%8F%A3%E6%B5%8B%E8%AF%95%E5%8F%8A%E4%BD%BF%E7%94%A8/TestString.cpp");
size_t pos1 = url.find(':');
string url1 = url.substr(0, pos1 - 0);
cout << url1 << endl;
size_t pos2 = url.find('/', pos1 + 3);
string url2 = url.substr(pos1 + 3, pos2 - (pos1 + 3));
cout << url2 << endl;
string url3 = url.substr(pos2 + 1);
cout << url3 << endl;
}
int main()
{
test_string12();
return 0;
}
void test_string13()
{
string str("Please, replace the vowels in this sentence by asterisks.");
size_t found = str.find_first_of("aeiou");
while (found != string::npos)
{
str[found] = '*';
found = str.find_first_of("aeiou", found + 1);
}
cout << str << '\n';
}
int main()
{
test_string13();
return 0;
}
void SplitFilename(const string& str)
{
cout << "Splitting: " << str << '\n';
size_t found = str.find_last_of("/\\");
cout << " path: " << str.substr(0, found) << '\n';
cout << " file: " << str.substr(found + 1) << '\n';
}
int main()
{
string str1("/usr/bin/man");
string str2("c:\\windows\\winhelp.exe");
SplitFilename(str1);
SplitFilename(str2);
return 0;
}
void test_string14()
{
string s1 = "hello";
string s2 = "world";
string ret1 = s1 + s2;
cout << ret1 << endl;
string ret2 = s1 + "xxxxx";
cout << ret2 << endl;
string ret3 = "xxxxx" + s1;
cout << ret3 << endl;
//字典序比较
cout << (s1 < s2) << endl;
}
int main()
{
test_string14();
return 0;
}
一个题目:
- 字符串最后一个单词的长度
#include <iostream>
using namespace std;
int main()
{
string str;
//默认规定空格或者换行是多个值之间分割
//cin >> str;
//获取一行中包含空格,不能用>>
getline(cin, str);
size_t pos = str.rfind(' ');
cout << str.size() - (pos + 1) << endl;
return 0;
}
- 输入多行字符依次打印:
int main()
{
//默认规定空格或者换行是多个值之间分割
string str;
//ctrl + z 就可以结束
while (cin >> str)
{
cout << str << endl;
}
return 0;
}
- 字符串转整形,整形转字符串
int main()
{
//atoi itoa
//to_string
int x = 0, y = 0;
cin >> x >> y;
string str = to_string(x + y);
cout << str << endl;
int z = stoi(str);
return 0;
}