【STL】string类用法介绍及部分接口的模拟实现
1.前言
string即字符串,我们知道在C语言中字符串是以‘\0’结尾的字符合集,同时C标准库中也提供了str系列如strlen、strcpy等一系列库函数,但它们与字符串分离,这与C++面向对象编程思想不合,因此在C++库中添加了string类以供使用者使用,下面我们就来介绍一下。
2.string类的介绍及使用
注:本文在此仅介绍部分经常使用的接口函数,若想查看所有用法请参考
cplusplus
2.1 string类对象的构造
函数名 | 功能 |
---|---|
string() | 构造空的string类对象 |
string(const char s)* | 用C-string来构造string类对象 |
string(size_t n,char c) | string类对象中包含n个c字符 |
string(const string&s) | 通过s拷贝构造 |
eg:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1;//注意不要用成string s1();哦,注意区分
string s2("hello world");
string s3(5, 'x');
string s4(s2);
//打印s1~s4
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
return 0;
}
运行结果如下:
2.2 string类对象的容量操作
2.2.1 size()
1.作用:返回字符串的有效长度,不包括’\0’,且不会改变string中有效元素个数
2.使用:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1("hello world");//不算'\0'共11个字符
cout << s1.size() << endl;
return 0;
}
运行结果如下:
上面提到size()的返回值不包括’\0’,那么在string类对象中其字符串结尾是否存在’\0’呢?我们通过下面代码调试查看一下:
int main()
{
string s1("hello world");
cout << s1.size() << endl;
char temp = s1[s1.size()];
return 0;
}
通过调试窗口我们发现是存在’\0’的,但其不算在size大小之中:
2.2.2 reserve()
1.作用:为string类对象至少预留指定大小的内存空间。
2.使用:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1("hello world");
cout << "before reserve's capacity:" << s1.capacity() << endl;//打印reserve前s1对象的空间大小
s1.reserve(50);
cout << "before reserve's capacity:" << s1.capacity() << endl;//打印reserve后s1对象的空间大小
return 0;
}
结果如下:
扩展:有人会好奇为什么reserve后s1空间大小不是50,这是因为在VS下其扩容是以1.5倍左右扩容:
int main()
{
string s1("x");
int capacity = s1.capacity();
cout << capacity << endl;
while (capacity < 10000)
{
s1.append("xxxxxxxxxx");//append函数作用是在字符串后添加10个x,当空间大小不够时底层会使用reserve扩容
//每次扩容后查看扩容后的空间大小
if (s1.capacity() != capacity)
{
capacity = s1.capacity();
cout << capacity << endl;
}
}
return 0;
}
输出结果:
15
31
47
70
105
157
235
352
528
792
1188
1782
2673
4009
6013
9019
13528
可以发现每次扩容大约是上次的1.5倍
在VS2022下当指示预留空间的大小小于之前已有的空间大小时不会进行缩容:
int main()
{
string s1("hello world");
s1.reserve(100);
cout << "reserve(100) capacity:" << s1.capacity() << endl;
s1.reserve(50);
cout << "reserve(50) capacity:" << s1.capacity() << endl;
return 0;
}
结果如下:
注:在不同情况下reserve的扩容情况不同,比如在Linux g++下reserve每次扩容的大小近似2倍。
2.2.3 resize()
1.作用:调整string类对象的长度。若新长度大于当前长度,会在原字符串末尾添加指定字符(默认为空字符’\0’)来达到新长度;若新长度小于当前长度,则会截断字符串。
2.使用:
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "abc";
cout << "原始字符串: " << str << endl;
cout << "原始capacity:" << str.capacity() << endl;
// 增大字符串长度,默认用 '\0' 填充,指定大小小于capacity时capacity不变
str.resize(10);
cout << "增大长度后的字符串: " << str << endl;
cout << "增大长度后capacity:" << str.capacity() << endl;
// 增大字符串长度,用 'x' 填充,指定大小大于capacity时capacity变大
str.resize(20, 'x');
cout << "再次增大长度后的字符串: " << str << endl;
cout << "再次增大长度后capacity:" << str.capacity() << endl;
//减小字符串长度,指定大小小于capacity时capacity不变,相当于尾删
str.resize(5);
cout << "缩小长度后的字符串: " << str << endl;
cout << "缩小长度后capacity:" << str.capacity() << endl;
return 0;
}
结果如下:
注意:关于为什么再次增大后打印出来的x数量不是17个以及缩小长度后打印出来的字符串只有abc的问题,原因是第一次增大字符串长度时是用’\0’填充,其要占用字符串的空间大小,但其不作为结束符的标志(即不像C语言字符串一样遇到’\0’就结束)。
2.2.4 clear()
1.移除string类对象中的所有字符,使字符串长度变为 0。调用该方法后,字符串就为空了,但它所占的空间通常不会改变。
2.使用:
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "Hello, World!";
cout << "原始字符串: " << str << endl;
cout << "原始字符串长度: " << str.size() << endl;
cout << "原始字符串容量: " << str.capacity() << endl;
// 清空字符串
str.clear();
cout << "清空后的字符串: " << str << endl;
cout << "清空后的字符串长度: " << str.size() << endl;
cout << "清空后的字符串容量: " << str.capacity() << endl;
return 0;
}
结果如下:
2.2.5 其它容量操作
函数名 | 功能 |
---|---|
length() | 与size()功能相同 |
capacity() | 返回空间大小 |
empty() | 检测字符串是否为空串,是返回true,否则返回false |
2.3 string类对象的访问及遍历
2.3.1 operator[]
1.作用:重载[],返回pos位置的字符
2.使用:
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "Hello, World!";
cout << "修改pos=4前:" << str[4] << endl;//通过pos位置打印出pos位置的字符
str[4] = 'x';//修改pos位置的值
cout << "修改pos=4后:" << str[4] << endl;
return 0;
}
2.3.2 正向迭代器begin()+end()和反向迭代器rbegin()+rend()
1.作用:begin()、end()、 rbegin()、rend()都是迭代器相关的成员函数,在string类中用于标记字符串的起始结束位置,通过使用迭代器可以进行string类对象的遍历
2.使用:
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "Hello, World!";
cout << "正向遍历begin()+end():";
string::iterator it1 = str.begin();//正向
while (it1 != str.end())
{
cout << *it1;
++it1;
}
cout << endl;
cout << "反向遍历begin()+end():";
string::reverse_iterator it2 = str.rbegin();//反向
while (it2 != str.rend())
{
cout << *it2;
++it2;
}
return 0;
}
结果如下:
2.3.3 范围for的使用
使用:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str = "Hello, World!";
//auto自动识别类型
for (auto ch : str)
{
cout << ch;
}
return 0;
}
输出结果:
2.4 string类的增删查改操作
2.4.1 push_back()
1.作用:将一个字符添加到string类对象的末尾,使字符串的长度增加1(size+1)
2.使用:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str = "Hello, World!";
cout << "before push_back:" << str << endl;
str.push_back('x');
cout << "after push_back('x'):" << str << endl;
char ch = 'y';
str.push_back(ch);
cout << "after push_back(ch):" << str << endl;
return 0;
}
输出结果如下:
2.4.2 append()
1.作用:在字符串后追加一段字符串
2.使用:
#include <iostream>
#include <string>
using namespace std;
int main()
{
//尾插一段字符串
string s1("hello ");
cout << "s1 before append:" << s1 << endl;
s1.append("world");
cout << "s1 after append:" << s1 << endl;
//尾插另一段字符串下标i~j的内容,j不写时默认从i位置到该字符串结束位置
string s2("hello");
string s3("hello world");
cout << "s2 before append:" << s2 << endl;
s2.append(s3,5,10);
cout << "s2 after append:" << s2 << endl;
return 0;
}
结果如下:
2.4.3 operator+=
1.作用:重载+=,在字符串后追加字符串
2.使用:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1("hello ");
string s2("world");
char ch = 'x';
cout << s1 << endl;
//尾插一段字符串
s1 += "world";
cout << s1 << endl;
//尾插另一个string类的str内容
s1 += s2;
cout << s1 << endl;
//尾插一个字符
s1 += ch;
cout << s1 << endl;
return 0;
}
结果如下:
2.4.4 insert()
1.作用:在 string 类对象的任意位置插入新的内容
2.使用:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1("hello");
string s2("world ");
const char* ch = "xxxxxxxx";
cout << s1 << endl;
s1.insert(2, 3, 'n');//在下标2位置前插入3个x字符
cout << s1 << endl;
s1.insert(0, s2);//在下标0位置前插入s2字符串(头插s2)
cout << s1 << endl;
s1.insert(10, ch);//在下标10为之前插入一个C风格字符串
cout << s1 << endl;
s1.insert(s1.begin(), 'x');//可以结合迭代器使用
cout << s1 << endl;
return 0;
}
结果如下:
2.4.5 erase()
1.作用:从字符串中语出指定位置为字符或一段字符序列
2.使用:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1 = "hello world";
s1.erase(2, 5); //删除s1下标从2开始的5个字符
cout << s1 << endl;
string s2 = "hello world";
s2.erase(2); //删除s1下标从2开始的剩余字符
cout << s2 << endl;
string s3 = "hello world";
s3.erase(); //删除整个字符串
cout << s3 << endl;
string s4 = "hello, world";
//结合迭代器使用
//s4.erase(s4.begin());//头删
s4.erase(s4.begin(), s4.begin()+4);// 删除指定区域的内容
cout << s4 << endl;
return 0;
}
结果如下:
2.4.6 find()
1.作用:在string类对象中搜索指定的内容,并返回该内容首次出现的起始位置(索引)。如果未找到指定内容,则返回 string::npos
,这是一个静态常量,表示无效的位置。
2.使用:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1("hello world");
string s2("world");
cout << "w:" << s1.find('o',5) << endl;//查找字符类型o,从下标为5的位置开始找
cout << "rld:" << s1.find("world") << endl;//查找C类型字符串,可在后面指定起始区域
cout << "s2:" << s1.find(s2) << endl;//查找string类型字符串,可在后面指定起始区域
cout << "npos:" << s1.find('x') << endl;//找不到返回npos
return 0;
}
结果如下:
2.4.7 substr()
1.作用:从调用它的string对象里提取一部分字符,生成一个新的string对象。
2.使用:
#include <iostream>
#include <string>
using namespace std;
int main() {
string s1 = "Hello, World!";
string subStr1 = s1.substr(7);//从下标为7的位置开始提取后面的字符串给subStr1
cout << "从下标7开始的子字符串: " << subStr1 << endl;
string subStr2 = s1.substr(0, 5);// 从下标0位置开始,提取长度为5的子字符串给subStr2
cout << "从下标0开始,长度为5的子字符串: " << subStr2 << endl;
return 0;
}
结果如下:
2.4.8 c_str()
1.作用:返回一个指向以 '\0'
结尾的字符数组(C 风格字符串)的指针,该字符数组包含了string对象中的数据。
2.使用:在 C++ 程序中,有时需要调用一些使用 C 风格字符串的 C 语言函数,例如 printf()、fopen() 等。由于这些函数只能处理以 '\0结尾的字符数组,此时就可以使用 c_str()方法将 string对象转换为 C 风格字符串。示例代码如下:
#include <iostream>
#include <string>
#include <cstdio>
using namespace std;
int main() {
string filename = "example.txt";
// 使用 c_str() 将 string 转换为 C 风格字符串
FILE* file = fopen(filename.c_str(), "r");
if (file) {
cout << "文件打开成功!" << endl;
fclose(file);
}
else {
cout << "文件打开失败!" << endl;
}
return 0;
}
关于C++string类的介绍暂时就到这里,其余用法可参考
cplusplus
3.string类部分模拟实现
对于string类的模拟实现这里不做详细原理介绍,仅提供一份个人写的参考代码。有任何问题欢迎讨论区提出,谢谢。
3.1 string.h
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
namespace mystring
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
const_iterator begin() const
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator end() const
{
return _str + _size;
}
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
//传统写法
/*string(const string& s)
{
_str = new char[s._capacity+1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
string& operator=(string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}*/
//会发生浅拷贝
/*string& operator=(string& s)
{
_str = s._str;
_size = s._size;
_capacity = s._capacity;
return *this;
}*/
//现代写法
string(const string& s)
{
string tmp(s._str);
swap(tmp);
}
string& operator=(string s)
{
swap(s);
return *this;
}
~string()
{
delete[] _str;
_size = 0;
_capacity = 0;
}
size_t size()
{
return _size;
}
const char* c_str() const
{
return _str;
}
const char& operator[](size_t pos) const;
char& operator[](size_t pos);
string& operator+=(char ch);
string& operator+=(const char* str);
void reserve(size_t n);
void push_back(char ch);
void append(const char* s);
void insert(size_t pos, char ch);
void insert(size_t pos, const char* str);
void erase(size_t pos, size_t len = npos);
void swap(string& s);
size_t find(char ch, size_t pos = 0);
size_t find(const char* str, size_t pos = 0);
string substr(size_t pos = 0, size_t len = npos);
void clear();
/*void Print_addr(int n = 0)
{
for (int i = 0; i < _size; i++)
{
printf("a[%d]%p\n", i, &_str[i]);
}
}*/
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
static const size_t npos = -1;
};
istream& operator>>(istream& in, string& str);
ostream& operator<<(ostream& out, string& str);
}
3.2 string.cpp
#define _CRT_SECURE_NO_WARNINGS
#include"string.h"
istream& mystring::operator>>(istream& in, string& str)
{
str.clear();
char buff[128];//一串一串写入
char ch = in.get();
int i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 127)
{
buff[i] = '\0';
str += buff;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
buff[i] = '\0';
str += buff;
}
return in;
}
ostream& mystring::operator<<(ostream& out, string& str)
{
for (auto e : str)
{
out << e;
}
return out;
}
const char& mystring::string::operator[](size_t pos) const
{
assert(pos >= 0 && pos < _size);
return *(_str + pos);
}
char& mystring::string::operator[](size_t pos)
{
assert(pos >= 0 && pos < _size);
return *(_str + pos);
}
void mystring::string::reserve(size_t n)
{
assert(n > 0);
if (n > _capacity)
{
char* tmp = new char[n];
strcpy(tmp, _str);
free(_str);
_str = tmp;
_capacity = n;
}
}
void mystring::string::push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 1.5 * _capacity);
}
_str[_size] = ch;
_size++;
}
void mystring::string::append(const char* s)
{
size_t s_len = strlen(s);
while (_size + s_len > _capacity)
{
reserve(_capacity == 0 ? 4 : 1.5 * _capacity);
}
strcpy(_str + _size, s);
_size += s_len;
}
void mystring::string::insert(size_t pos, char ch)
{
assert(pos >= 0&&pos<=_size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 1.5 * _capacity);
}
for (int i = _size; i > pos; i--)
{
_str[i] = _str[i-1];
}
_str[pos] = ch;
_size += 1;
}
void mystring::string::insert(size_t pos, const char* str)
{
assert(pos >= 0 && pos <= _size);
int str_len = strlen(str);
while (_size + str_len > _capacity)
{
reserve(_capacity + str_len);
}
for (int i = _size + str_len -1; i > pos + str_len - 1; i--)
{
_str[i] = _str[i - str_len];
}
strncpy(_str + pos, str,str_len);
_size += str_len;
}
mystring::string& mystring::string::operator+=(char ch)
{
push_back(ch);
return *this;
}
mystring::string& mystring::string::operator+=(const char* str)
{
append(str);
/*int str_len = strlen(str);
for (int i = 0; i < str_len; i++)
{
*this += str[i];
}*/
return *this;
}
void mystring::string::erase(size_t pos, size_t len)
{
assert(pos >= 0 && pos < _size);
if (pos + len > _size)
{
_size = pos;
}
else
{
for (int i = pos; i < _size - len; i++)
{
_str[i] = _str[i + len];
}
_size -= len;
}
}
void mystring::string::swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
size_t mystring::string::find(char ch, size_t pos)
{
while (pos < _size)
{
if (_str[pos] == ch)
return pos;
pos++;
}
return npos;
}
size_t mystring::string::find(const char* str, size_t pos)
{
const char* judge = strstr(_str + pos, str);
if (judge == nullptr)
return npos;
else
return judge - _str;
}
mystring::string mystring::string::substr(size_t pos, size_t len)
{
assert(pos < _size);
size_t end = pos + len;
if (len == npos || end >= _size)
{
end = _size;
}
string str;
str.reserve(end - pos);
for (size_t i = pos; i < end; i++)
{
str += _str[i];
}
return str;
}
void mystring::string::clear()
{
_size = 0;
_str[0] = '\0';
}
3.3 test.cpp(个人部分测试用例)
#define _CRT_SECURE_NO_WARNINGS
#include "string.h"
void test1()
{
mystring::string s1;
s1.push_back('a');
s1.reserve(20);
}
void test2()
{
mystring::string s1("123456");
s1.append("hello world");
}
void test3()
{
mystring::string s("hello world");
mystring::string s1 = s;
mystring::string::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it;
++it;
}
cout << endl;
for (auto& e : s1)
{
cout << e;
}
const mystring::string s2("const test");
mystring::string::const_iterator it1 = s2.begin();
while (it1 != s2.end())
{
cout << *it1;
++it1;
}
}
void test4()
{
mystring::string s("hello world!");
s.insert(0, "xxxxx");
cout << s << endl;
}
void test5()
{
mystring::string s("hello world!");
s += "xxxxxxxxxxx";
cout << s << endl;
cout <<s.size();
}
void test6()
{
mystring::string s("hello world!");
cout << s << endl;
s.erase(0);
cout << s;
}
void test7()
{
mystring::string s1("hello world!");
mystring::string s2;
s2 = s1;
cout << s2 << endl;
cout << s1;
}
void test8()
{
mystring::string s1("hello world!");
mystring::string s2(s1);
cout << &s2 << endl;
cout << s2 << endl;
cout << &s1 <<endl;
cout << s1;
}
void test9()
{
mystring::string s1("hello worlld!");
//s1.Print_addr();
size_t found = s1.find('o',5);
cout << found << endl;
size_t fond = s1.find("ll",5);
cout << fond;
}
void test10()
{
mystring::string s1("hello worlld!");
mystring::string s2 = s1.substr();
cout << s2;
}
void test11()
{
mystring::string s1;
cin >> s1;
cout << s1;
}
void test12()
{
mystring::string s1("hello worlld!");
s1 += 'a';
s1 += " nihao shijie";
cout << s1;
}
int main()
{
//test12();
/*string s("hello world!");
s.erase(0);*/
/*string s("hello world!");
s.insert(0, "xxxxx");
cout << s << endl;*/
return 0;
}
本文到此结束,有任何问题包括有想要作者介绍一下的string接口都欢迎私信、评论区提出,看到后会抽空对文章进行添加、修改,谢谢。