为什么学习string类?
C 语言中,字符串是以 '\0' 结尾的一些字符的集合,为了操作方便, C 标准库中提供了一些 str 系列 的库函数,但是这些库函数与字符串是分离开的,不太符合OOP 的思想,而且底层空间需要用户 自己管理,稍不留神可能还会越界访问。
标准库中的string类
推荐文档:string - C++ Reference
在 使用 string 类时,必须包含 #include 头文件以及 using namespace std ;
在推荐文档中,我们可以看到成员功能有三个
constructor(创建)
包含7种用法

实操
cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
//1
string s1;//无内容
//4
string s2("hello worle");
//2
string s3(s2);//拷贝构造
cout << s1 << endl << s2 << endl << s3 << endl;
cin >> s1;
cout << s1 << endl;
//3
string s4(s2, 6, 10);//中间数字是字符的下标,最后一个数字是读取结束的下标吗,注意,如果没有,编译器自动填充 size_nops=-1
string s5(s2, 6);
cout << s4 << endl << s5 << endl;
//5
string s6("hello world", 7);
cout << s6 << endl;
//6
string s7(10, 'a');
cout << s7 << endl;
}
结果

destructor(析构)
不需要自己析构

operator=(赋值)

auto和范围for
auto
用 auto 声明指针类型时,用 auto 和 auto* 没有任何区别,但用 auto 声明引用类型时则必须加 &
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际 只对第一个类型进行推导,然后用推导出来的类型定义其他变量 。
cppint main() { string s1("hello world"); auto a = 1; auto b = 's'; auto c = "sjlf"; auto e = 1, f = 22.2;//不可,必须为同一类型 return 0; }注意事项
auto 不能作为函数的参数,可以做返回值,但是建议谨慎使用
auto 不能直接用来声明数组
范围for
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此 C++11中引入了基于范围的for循环。**for循环后的括号由冒号":"**分为两部分:第一部分是范围
cpp
int main()
{
string s1("hello world");
for (auto ch1 : s1)
cout << ch1;
for (auto& ch2 : s1)//加引用可以修改s1
cout << ch2;
return 0;
}
string类的常用接口
constructor在上述已讲
string类对象的容量操作
size (重点) 返回字符串有效字符长度
length 返回字符串有效字符长度
capacity 返回空间总大小
empty (重点) 检测字符串释放为空串,是返回 true ,否则返回 false
clear (重点) 清空有效字符
reserve (重点) 为字符串预留空间 * *
resize (重点) 将有效字符的个数该成 n 个,多出的空间用字符 c 填充
cpp
string s1("hello world");
cout<<s1.size();//字符串有效长度
cout<<s1.length();//字符串有效长度 两个功能相同的接口是历史原因
cout<<s1.capacity();
s1.clear();//清理字符串,一般只清数据,不清容量
s1.reserve(100);//预留空间,避免频繁扩容,影响效率
s1.resize(100,'x');//调整字符串大小多出的空间用字符x填充
访问字符串
operator[]
C++重载了[],使得我们可以像数组一样访问字符串
cpp
string s1("hello world");
//1
for (int i = 0; i < s1.size(); i++)
cout << s1[i] ;
begin+end
begin 获取一个字符的迭代器 + end 获取最后一个字符下一个位 置的迭代器
cpp
string s1("hello world");
auto b = s1.begin();//b指向h
auto c = s1.end();//c是最后一位有效字符的下一位
rbegin+rend
rend获取一个字符的迭代器 + rbegin 获取最后一个字符下一个位 置的迭代器
cpp
string s1("hello world");
auto b = s1.rbegin();
auto c = s1.rend();
string的对象修改操作
push_back 在字符串后尾插字符 c
append 在字符串后追加一个字符串
operator+= ( 重点) 在字符串后追加字符串 str
c_str ( 重点 ) 返回 C 格式字符串
find + npos ( 重点) 从字符串 pos 位置开始往后找字符 c ,返回该字符在字符串中的 位置
rfind 从字符串 pos 位置开始往前找字符 c ,返回该字符在字符串中的 位置
substr 在 str 中从 pos 位置开始,截取 n 个字符,然后将其返回
cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
//push_back
string s1("hello");
s1.push_back('x');//在s1后面加上字符x
//append
s1.append(" world");//在s1后面加上字符串
//operator+=
s1 += 'x';
s1 += "world";
//c_str
const string ch = s1.c_str();
const char*ch1 = s1.c_str();
cout << ch << endl;
cout << ch1 << endl;
//find
size_t pos = s1.find("world");
cout << pos << endl;
while (size_t pos =s1.find("x") != std::string::npos)
{
cout << pos << endl;
s1.find("x", pos + 1);//从上一个找到x的下标的下一个位置开始找
}
//rind
size_t pos1 = s1.rfind("x");
cout << pos1 << endl;
//substr
cout << s1.substr(0, 5) << endl;
return 0;
}
getline
获取一行字符串
在正式讲解此题之前,我们先看一道题

有上面的find和rfind,此时你可能想到用rfind查找最后一个空格,然后有效长度减去似乎就可以了
但我们要知道cin先是存储在缓存区的,它默认以换行符或空格结束,所以此处使用cin只能输入一个字符
此处再看getline
在上图中getling第二个使用形式是默认以换行符为结束
第一个使用形式则是可以自定义结束标志
cpp
getline(cin, s1);//默认以换行符为结束
getline(cin, s1,'x');//自定义x为结束标志
string的模拟实现
浅拷贝
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来 。如果 对象中管理资源 ,最后就会 导致 多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该 资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规
cpp
string(const char* str="")//无参数,自动补上\0
{
_size = _capacity = strlen(str);//此处走函数构造不使用初始化列表是为了在不改变私有成员顺序的前提下,可以先初始化_size为后面使用
//_capacity不包括\0
_str = new char[_capacity + 1];//为\0预留空间
strcpy_custom(_str, str);
}
深拷贝
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给 出。一般情况都是按照深拷贝方式提供。
cpp
string(const char* s)//深拷贝
{
char* _str = new char[strlen(s)];
strcpy(_str, s);
}
cpp
// s2 = s1 //特殊情况,s2空间小于s1 s2的空间远大于s1
string& string::operator=(const string& s)
{
if (this != &s)
{
delete []_str;
//此处清除s2再进行赋值就算为了应对上面的特殊情况
_str = new char[s._capacity+1];
strcpy(_str,s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
现代写法
cpp
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
// s2(s1)
// 现代写法
string(const string& s)
{
//建议此处要自己对私有成员进行赋值,要不然编译器一般不会对内置类型赋值,或者赋值为随机值
string temp(s._str);//此处是拿s里面的字符串进行赋值,调拷贝构造函数,此处temp既有s的字符串,也有size capacity这些的
swap(temp);
}
cpp
string& string::operator=(const string& s)
{
string temp(s);
swap(temp);
}
但现代写法的运行效率与之前的写法之间没有区别
课后建议
自己实现string的类模拟,包含常用的重点接口
去力扣上做有关字符的练习,巩固知识和加强理解
