C++初阶第九篇——string类(string类中一些常见接口的用法与介绍+string类的模拟实现)
Posted 呆呆兽学编程
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++初阶第九篇——string类(string类中一些常见接口的用法与介绍+string类的模拟实现)相关的知识,希望对你有一定的参考价值。
⭐️从今天开始,我就要给大家介绍STL的内容了,今天我先为大家介绍一下第一号人物——string类,我会先介绍它的一些歌常见接口以及用法,然后再模拟实现它。
⭐️博客代码已上传至gitee:https://gitee.com/byte-binxin/cpp-class-code
目录
🌏了解string类
string类 就是在C语言的字符串做了一些处理,C语言中有一些处理字符串的库函数,但是字符串和库函数是分离的,而string类提供了一些对字符串处理的接口,把这些接口和字符串封装成一个类,更加地方便我们使用和操作。
总结地说:
- string是表示字符串的字符串类
- string类的接口和一些常规容器的接口基本相同,而且添加了一些专门用了操作string的常规操作
- string的底层:typedef basic_string<char, char_traits, allocator> string;
- 不能操作多字节或者变长字符的序列
- 使用时包含sting的头文件,而且要展开std
🌏string类常见的接口
🌲string的几个构造函数
- 无参默认构造函数: string() 构造类的空对象,即空字符串
- 有参构造函数: string(size_t n, char c) 用n个字符c来构造string类对象
- 有参构造函数: string(const char* s) 用C字符串来构造string类对象
- 拷贝构造函数 string(const string& s)
实例演示:
void TestString1()
// 构造函数
string s1;// 重点
string s2("hello world");// 用C string来构造string类对象 重点
string s3(s2);// 拷贝构造函数
string s4(10, 'a'); // 用n个字符'a'构造一个类对象 重点
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
代码运行结果如下:
🌲string类的三种遍历方式
- for+operator[]访问
- 迭代器遍历
- 范围for(会被编译器替换成迭代器来遍历)
实例演示:
void TestString2()
string s("hello world");
// 3种遍历方式
// 1.for+operator[]
for (size_t i = 0; i < s.size(); ++i)
cout << s[i];
cout << endl;
// 2.迭代器 iterator 不一定是指针,但是像
string::iterator it = s.begin(); // auto it = s.begin();
while (it < s.end())
cout << *it;
++it;
cout << endl;
// 3.范围for 原理被迭代器替换
for (auto e : s)
cout << e;
cout << endl;
代码运行结果如下:
其中迭代器是属于string类中的一个成员,它可能是指针,但不一定是指针,string类中它其实就是一个指针,但是其他容器就不一定了,后面会介绍。
🌲string类的四种迭代器
按方向分: 有正向迭代器和反向迭代器(iterator和reverse_iterator)分别配合being()、end()和rbegin()、rend()使用
按属性分: 有普通迭代器和const迭代器 (iterator const_iterator | reverse_iterator const_reverse_iterator)
实例演示:
void TestString3()
string s("hello world");
string::reverse_iterator rit = s.rbegin();
while (rit < s.rend())
cout << *rit;
++rit;
cout << endl;
代码运行结果如下:
注意: 对于const的类对象,要是有const的迭代器,否则会报错。
🌲string类的大小操作
- size和length 返回字符串的有效长度
- capacity 返回有效空间的大小
- clear 清空有效字符,不改变底层空间的大小
- empty 返回字符串是否为空的状态
- reverse reverse(size_t n = 0); 为字符串预留空间
- resize resize(size_t n, char c = ‘\\0’); 改变字符串有效长度,多出的空间用字符c补充
实例演示:
void TestString4()
string s("hello world");
cout << s.size() << endl; // 为了与其他容器的接口保持一致
cout << s.length() << endl;
cout << s.capacity() << endl;
cout << "-----------------------" << endl;
s.clear();// 不改变底层空间的大小
cout << s.size() << endl;
cout << s.capacity() << endl;// 不变
cout << "-----------------------" << endl;
// 将有效字符个数增加为10个,多余的用'x'补充
s.resize(10, 'x');
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << "-----------------------" << endl;
// 将有效字符个数增加为15个,多余的用'\\0'补充
s.resize(15);
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << "-----------------------" << endl;
// 将有效字符个数缩小为5个
s.resize(5);
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << s << endl;
代码运行结果如下:
总结: 对于resize,当字符增多时,多出的空间用使用者给的字符补充,没给就使用缺省参数’\\0’赋值,元素增多可能会扩大底层空间大小,元素减小不会堆底层空间大小有影响。
🌲string类的增删查改操作
- push_back 在字符串后尾插字符‘c’
- append 在字符串后尾插一段字符串
- operator+= 在字符串后追加字符串str
- insert 在pos位置插入一个字符或者一段字符串
- erase 从pos位置开始,删去n个字符
实例演示:
void TestString8()
string s;
s.push_back('h');
s.push_back('e');
s.push_back('l');
s.push_back('l');
s += 'o';
cout << s << endl;
s.append(" wo");
s += "rld";
cout << s << endl;
代码运行结果如下:
-
c_str 返回C格式的字符串
-
find 没找到就返回npos的结果(string::npos = -1)
-
rfind 反向从pos的位置开始寻找
-
substr string substr (size_t pos = 0, size_t len = npos) const 从pos位置开始,截取n个字符
实例演示:
// 分割网址 http(s)+网站名+资源
void SplitUrl(string& url)
// https://leetcode-cn.com/problemset/all/
size_t pos1 = url.find(':');
cout << url.substr(0, pos1) << " ";
size_t pos2 = url.find('/', pos1 + 3);
cout << url.substr(pos1 + 3, pos2 - (pos1 + 3)) << " ";
cout << url.substr(pos2, url.size() - pos2) << endl;
void TestString7()
string s("hello ");
s += "world";// '\\0'以空格显示
cout << s << endl;// 使用运算符重载 operator<<
cout << s.c_str() << endl;// 以C语言的方式打印
// basic_string substr(size_type pos = 0,size_type n = npos) const 从pos位置开始打印n个字符
string file1("string.cpp");
size_t pos = file1.find('.');
if (pos != string::npos)
cout << file1.substr(pos) << endl;
string file2("file.tar.zip");
pos = file2.rfind('.');
if (pos != string::npos)
cout << file2.substr(pos) << endl;
string url1("http://www.cplusplus.com/search.do?q=npos");
string url2("https://leetcode-cn.com/problemset/all/");
SplitUrl(url1);
SplitUrl(url2);
代码运行结果如下:
🌲string类的非成员函数
- operator+ 传值返回加长后的string,尽量少用,因为传值返回会导致深拷贝的效率降低
- operator>> 重载输入运算符
- operator<< 重载输出运算符
- getline 获取一行字符串
- realational operators 大小比较
实例演示:
void TestString9()
string s;
cin >> s;
cout << s << endl;
s = s + " world";
cout << s << endl;
代码运行结果如下:
🌏string类的模拟实现
🍯string类的成员
string类是字符串类,实现使用了一个C中的字符串,为了可以扩容改变空间大小,我们选择在堆上开辟空间,然后用一个字符指针指向这块空间,和我们之前数据结构中的顺序表实现有类似之处。
private:
char* _str;
size_t _size;// 有效字符个数
size_t _capacity;// 能存储的有效字符的个数 '\\0'不是有效字符,是标识结束的字符
public:
static size_t npos;
size_t string::npos = -1;
🍯string类的构造函数和析构函数
- 有参构造函数的实现,缺省值给空字符串,因为要存放’\\0’,所以要多开一个空间的大小,’\\0’是无效字符,不算有效大小
string(const char* str = "")
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];// 因为要存放'\\0','\\0'是无效字符,不算有效大小
strcpy(_str, str);
- 析构函数的实现,清理空间资源
~string()
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
🍯深浅拷贝
前面我们就提到过,浅拷贝就是简单的字节序拷贝,为了更好地理解深拷贝和浅拷贝,我来分开为大家讲解:
下面是我们用编译器的浅拷贝实现类对象的拷贝:
string s1("hello world");
string s2(s1);
代码运行结果如下:
编译器直接报错了,为什么会出现这种问题呢?其实我们前面有提到过
所以,为了避免释放两次空间,我们要进行深拷贝,深拷贝就是给新的对象出现开一块空间,这样就不是简单的字节序拷贝了。
代码实现如下:
string(const string& s)
// 深拷贝,另外开一块空间
_size = s._size;
_capacity = s._capacity;
_str = new char[_capacity + 1];
strcpy(_str, s._str);
代码运行结果:
这次代码就很好地跑起来了。
operator=的实现 与拷贝构造的实现很相似,要进行深拷贝,否则会出问题。
string& operator=(const string& s)
if (this != &s)// 防止自己给自己赋值
delete[] _str;
_size = s._size;
_capacity = s._capacity;
_str = new char[_capacity + 1];
strcpy(_str, s._str);
return *this;
🍯string类的访问和迭代器的实现
- operator[]的实现 为了让这块空间的值可以被修改,所以我们选择传引用返回
char& operator[](size_t i)
assert(i < _size);
return _str[i];
- 迭代器 string类的迭代器的本质其实是char*的别名,就是一个指针的用法,注意其中begin和end不能写其他的单词,不然编译器识别不出迭代器就会导致范围for使用报错
typedef char* iterator;
iterator begin()
return _str;
iterator end()
return _str + _size;
- c_str 直接返回底层的字符指针即可
const char* c_str() const
return _str;
🍯string类的增删操作的实现
reserve 预留出一片空间,我们要开一段新空间,然后把旧空间的数据拷贝到新空间上去,然后释放旧空间,空间都要多开一个,来存放’\\0’
void reverse(size_t n)
if (n > _capacity)
char* newStr = new char[n + 1];
strcpy(newStr, _str);
delete[] _str;
_str = newStr;
_capacity = n;
push_back 我们以2倍的方式增容,复用reverse的代码
void push_back(char ch)
// 扩容
if (_size == _capcaity)
size_t newcapacity = _capcaity == 0 ? 2 : _capcaity * 2;
reverse(newcapacity);
_str[_size] = ch;
_size++;
_str[_size] = '\\0';//注意
append
void append(const char* str)
size_t len = strlen(str);
// 扩容
if (len + _size > _capacity)
size_t newcapacity = len + _size;
reverse(newcapacity);
strcpy(_str + _size, str);
_size += len;
operator+= 这个函数有两个重载,一个是尾插字符串,一个是尾插字符,我们这里可以分别复用append和push_back的代码
string& operator+=(char ch)
push_back(ch);
return *this;
string& operator+=(const char* s)
append(s);
return *this;
insert有两个重载,一个是在pos位置插入一个字符,一个是在pos位置插入一段字符串,同时我们还有判断pos的合理性,为了能够拷贝重叠区间的字符串,我们可以使用memmove库函数来操作,而不是memcpy。
string& insert(size_t pos, char ch)
assert(pos <= _size);
// 扩容
if (_size == _capacity)
size_t newcapacity = _capacity == 0 ? 2 : _capacity * 2;
reverse(newcapacity);
memmove(_str + pos + 1, _str + pos, sizeof(char) * (_size - pos));
_str[pos] = ch;
_size++;
_str[_size] = '\\0';// 注意
return *this;
string& insert(size_t pos, const char* s)
assert(pos <= _size);
size_t len = strlen(s);
// 扩容
if (len + _size > _capacity)
size_t newcapcity = len + _size;
reverse(newcapcity);
memmove(_str + pos + len, _str + pos, sizeof(char) * (_size - pos));
// strcpy(_str + pos, s); 最后会把'\\0'拷过去
strncpy(_str + pos, s, len);
_size += len;
_str[_size] = '\\0';// 注意
return *this;
我们还可以思考这样一个问题,我们是不是可以复用insert的两个重载函数实现push_back和append,如下:
void push_back(char ch)
insert(_size, ch);
void append(const char* str)
insert(_size, str);
erase 在pos位置删去n个字符
string& erase(size_t pos, size_t len = npos)
assert(pos < _size);
if (len >= _size - pos)// 不能写成len+pos>=_size 因为当len是npos时,加上pos之后就变小了
_size = pos;
_str[_size] = '\\0';
else
memmove(_str + pos, _str + pos + len, sizeof(char) * (_size - (pos + len)));
_size -= len;
_str[_size] = '\\0';
return *this;
🍯string类的大小操作的模拟实现
size和capacity
size_t size() const
return _size;
size_t capacity() const
return _capacity;
resize 要考虑改变后的大小是否大于当前空间,考虑是否需要扩容,记得最后加一个’\\0’,表示字符串结尾
void resize(size_t n, char ch = '\\0')
if (n > _capacity)
以上是关于C++初阶第九篇——string类(string类中一些常见接口的用法与介绍+string类的模拟实现)的主要内容,如果未能解决你的问题,请参考以下文章
数据结构初阶第九篇——八大经典排序算法总结(图解+动图演示+代码实现+八大排序比较)
C++初阶第六篇——类和对象(下)(初始化列表+explicit关键字+static成员+友元+内部类)