C++ STL string详解

1. string简介

C语言中,可以用字符数组来存储字符串,如:

char ch[] = "hello world";

C++中,可以使用string类对象来存储字符串,使用起来比C语言中的字符数组要方便得多,而且不用考虑容量的问题。

本篇博客,我会以实用为主,介绍string类常用的接口,包括常见的构造函数、容量操作、访问及遍历操作、修改操作、非成员函数等等。由于是实用为主,某些不常用的接口我不做介绍,读者在使用时请自行查询文档。接着通过这些知识做几道练习题,通过题目,了解这些接口实际的用法。最后,我会讲解string的底层实现原理,并模拟实现。

2. string类对象的构造

2.1 string();

无参的构造函数,可以构造出空的string类对象,即空字符串。

string s; // 等价于string s("");

2.2 string(const char* s);

用C-string来构造string类对象。

string s("hello world");
cout << s << endl;

输出:

hello world

2.3 string(size_t n, char c);

string类对象中包含n个字符c。

string s(5, 'a');
cout << s << endl;

输出:

aaaaa

2.4 string(const string& s);

拷贝构造函数,使用一个string类对象构造另一个string类对象。

string s1("hello world");
string s2(s1);
cout << s1 << endl;
cout << s2 << endl;

输出:

hello world
hello world

2.5 使用迭代器构造

template<class InputIterator>
string(InputIterator first, InputIterator last);

利用任意类型的迭代器进行构造。

string s1("hello world");
string s2(++s1.begin(), --s1.end());
cout << s2 << endl;

 输出:

ello worl

3. string类对象的容量操作

3.1 size

size_t size() const;

返回字符串有效字符长度,同length。引入size是为了与其他容器接口保持一致,我习惯使用size而不是length。

string s("hello world");
cout << s.size() << endl;

输出:

11

3.2 length

size_t length() const;

同size,返回字符串有效字符长度。

string s("hello world");
cout << s.length() << endl;

输出:

11

3.3 capacity

size_t capacity() const;

返回空间总大小,至少是size。

string s("hello world");
cout << s.capacity() << endl;

visual studio 2022环境下输出:

15

3.4 empty

bool empty() const;

检测字符串是否为空串,是返回true,否则返回false。

string s1;
cout << s1.empty() << endl;

输出: 

1

3.5 clear

void clear();

清空有效字符,一般不改变底层空间大小。

string s("hello world");
s.clear();
cout << s.empty() << endl;

输出:

1

3.6 reserve

void reserve(size_t n = 0);

为字符串预留空间,不改变有效元素个数。当n比底层空间大时,会扩容,扩容后空间至少为n;当n比底层空间小时,一般不改变底层空间。

string s;
s.reserve(100);
cout << s.capacity() << endl;

visual studio 2022环境下输出:

111

3.7 resize

void resize(size_t n, char c = '\0');

将有效字符的个数改成n个。当n比字符串长度小时,只保留前n个字符;当n比字符串长度大时,会在后面填充字符c,直到字符串长度为n;当n比字符串底层空间大时,会扩容,扩容后空间至少为n,相当于执行了reserve(n)。

string s;
s.resize(5, 'x');
cout << s << endl;
s.resize(7, 'y');
cout << s << endl;
s.resize(3);
cout << s << endl;

输出:

xxxxx
xxxxxyy
xxx

4. string类对象的访问及遍历操作

4.1 operator[]

char& operator[](size_t pos);
const char& operator[](size_t pos) const;

返回pos位置的字符。非const对象调用后返回的字符可读可写,const对象调用后返回的字符只读不写。pos必须在有效的下标范围内。

string s1("hello world");
// 可读可写
s1[0] = 'H'; // "Hello world"
cout << s1[0] << endl; // 'H'const string s2("hello world");
// 只读不写
// s2[0] = 'H'; // 错误用法
cout << s2[0] << endl; // 'h'

输出:

H
h

4.2 begin + end

iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;

begin获取指向第一个字符的迭代器,end获取指向最后一个字符的下一个位置的迭代器。

非const对象调用后返回的iterator指向的字符可读可写,const对象调用后返回的const_iterator指向的字符只读不写。

string s1("hello world");
string::iterator it = s1.begin();
// 可读可写
while (it != s1.end())
{(*it)++;cout << *it << " ";++it;
}
cout << endl;const string s2("hello world");
// 只读不写
string::const_iterator cit = s2.begin();
while (cit != s2.end())
{// (*cit)++; // 错误用法cout << *cit << " ";++cit;
}
cout << endl;

输出:

i f m m p ! x p s m e 
h e l l o   w o r l d 

4.3 rbegin + rend

reverse_iterator rbegin();
const_reverse_iterator rbegin() const;
reverse_iterator rend();
const_reverse_iterator rend() const;

同begin和end,只不过是反向的。非const对象调用后返回的reverse_iterator指向的字符可读可写,const对象调用后返回的const_reverse_iterator指向的字符只读不写。

string s1("hello world");
string::reverse_iterator rit = s1.rbegin();
// 可读可写
while (rit != s1.rend())
{(*rit)++;cout << *rit << " ";++rit;
}
cout << endl;const string s2("hello world");
// 只读不写
string::const_reverse_iterator crit = s2.rbegin();
while (crit != s2.rend())
{// (*crit)++; // 错误用法cout << *crit << " ";++crit;
}
cout << endl;

输出:

e m s p x ! p m m f i 
d l r o w   o l l e h 

4.4 范围for

遍历string类对象更简洁的形式,底层会转换为迭代器。

string s("hello world");
for (auto ch : s)
{cout << ch << " ";
}
cout << endl;

输出:

h e l l o   w o r l d 

5. string类对象的修改操作

5.1 push_back

void push_back(char c);

在字符串后尾插字符c。

string s("hello world");
s.push_back('!');
cout << s << endl;

输出:

hello world!

5.2 append

string& append(const string& str);
string& append(const char* s);
string& append(size_t n, char c);

在字符串后追加一个字符串。

string s1("abc");
string s2("defg");
s1.append(s2);
cout << s1 << endl;
s1.append("hijkl");
cout << s1 << endl;
s1.append(2, 'm');
cout << s1 << endl;

输出:

abcdefg
abcdefghijkl
abcdefghijklmm

5.3 operator+=

string& operator+=(const string& str);
string& operator+=(const char* s);
string& operator+=(char c);

在字符串后追加一个字符串或者字符。

string s1("abc");
string s2("defg");
s1 += s2;
cout << s1 << endl;
s1 += "hijkl";
cout << s1 << endl;
s1 += 'm';
cout << s1 << endl;

输出: 

abcdefg
abcdefghijkl
abcdefghijklm

5.4 c_str

const char* c_str() const;

返回一个C格式字符串,以字符'\0'结尾。

string s("abcde");
cout << s.c_str() << endl;

输出:

abcde

5.5 find

size_t find(const string& str, size_t pos = 0) const;
size_t find(const char* s, size_t pos = 0) const;
size_t find(char c, size_t pos = 0) const;
static const size_t npos = -1;

从pos位置开始,往后找字符串或字符,返回该字符串或字符第一次出现的位置。若找不到,返回npos。

string s1("hello world");
size_t ret = s1.find('w');
if (ret != string::npos)cout << s1[ret] << endl;ret = s1.find("wor");
if (ret != string::npos)cout << s1[ret] << endl;string s2("or");
ret = s1.find(s2);
if (ret != string::npos)cout << s1[ret] << endl;

输出:

w
w
o

5.6 rfind

size_t rfind(const string& str, size_t pos = npos) const;
size_t rfind(const char* s, size_t pos = npos) const;
size_t rfind(char c, size_t pos = npos) const;
static const size_t npos = -1;

类似find,从pos位置开始,往前找字符串或字符。相当于返回该字符串或字符最后一次出现的位置。若找不到,返回npos。

5.7 substr

string substr(size_t pos = 0, size_t len = npos) const;

从pos位置开始,向后截取len个字符并返回。若len太大,截取时超出字符串范围,则截取到字符串的尾部。

string s("hello world");
string ret = s.substr(6, 3);
cout << ret << endl;

输出:

wor

6. string类非成员函数

6.1 operator+

string operator+(const string& str1, const string& str2);
string operator+(const char* s, const string& str);
string operator+(const string& str, const char* s);
string operator+(char& c, const string& str);
string operator+(const string& str, char& c);

拼接字符串和字符串或者字符。尽可能少用,因为传值返回导致深拷贝,效率低。

string s1("hello ");
string s2("world");
cout << s1 + s2 << endl;
cout << s1 + "world" << endl;
cout << "hello " + s2 << endl;
cout << s1 + 'a' << endl;
cout << 'b' + s2 << endl;

输出:

hello world
hello world
hello world
hello a
bworld

6.2 operator>>

istream& operator>>(istream& in, string& str);

流插入运算符重载,以空格、换行作为分隔符。

string s("hello world");
cin >> s;
cout << s << endl;

输出示例(第一行为输入):

abc def
abc

6.3 operator<<

ostream& operator<<(ostream& out, const string& str);

流提取运算符重载。

string s("hello world");
cout << s << endl;

输出:

hello world

6.4 getline

istream& getline(istream& in, string& str, char delim = '\n');

获取字符并存储到字符串中,直到遇到字符delim,返回该字符串。该字符串不包括delim。一般用来获取一行字符串。

string s;
getline(cin, s);
cout << s << endl;

输出示例(第一行为输入):

abc def
abc def

6.5 关系运算符

bool operator>(const string& str1, const string& str2);
bool operator>(const string& str, const char* s);
bool operator>(const char* s, const string& str);
bool operator<(const string& str1, const string& str2);
bool operator<(const string& str, const char* s);
bool operator<(const char* s, const string& str);
bool operator>=(const string& str1, const string& str2);
bool operator>=(const string& str, const char* s);
bool operator>=(const char* s, const string& str);
bool operator<=(const string& str1, const string& str2);
bool operator<=(const string& str, const char* s);
bool operator<=(const char* s, const string& str);
bool operator==(const string& str1, const string& str2);
bool operator==(const string& str, const char* s);
bool operator==(const char* s, const string& str);
bool operator!=(const string& str1, const string& str2);
bool operator!=(const string& str, const char* s);
bool operator!=(const char* s, const string& str);

比较字符串的大小,比较的是字符串对应位置的字符的ASCII码。

7. 练习

7.1 仅仅反转字母

仅仅反转字母https://leetcode.cn/problems/reverse-only-letters/description/

定义左指针left和右指针right,两个指针从两边向中间移动,直到相遇为止。若遇到字母,就交换左右指针指向的字符。

class Solution {
public:string reverseOnlyLetters(string s) {int left = 0, right = s.size() - 1;while (left < right){// 左边找字母while (left < right && !isalpha(s[left]))++left;// 右边找字母while (left < right && !isalpha(s[right]))--right;swap(s[left++], s[right--]);}return s;}
};

7.2 找字符串中第一个只出现一次的字符

找字符串中第一个只出现一次的字符原题链接https://leetcode.cn/problems/first-unique-character-in-a-string/description/

字符串中只会出现小写字母,所以可以用计数排序的思路,定义数组countA记录每个字符出现的次数,把每个字符ch映射到下标为ch-'a'的位置。第一次遍历字符串,统计出每个字符出现的次数。第二次遍历字符串,查找只出现一次的字符。

class Solution {
public:int firstUniqChar(string s) {int countA[26] = {0};for (auto ch : s)countA[ch - 'a']++;for (int i = 0; i < s.size(); ++i)if (countA[s[i] - 'a'] == 1)return i;return -1;}
};

7.3 字符串里面最后一个单词的长度 

字符串里面最后一个单词的长度原题链接https://www.nowcoder.com/practice/8c949ea5f36f422594b306a2300315da?tpId=37&&tqId=21224&rp=5&ru=/activity/oj&qru=/ta/huawei/question-ranking

由于输入包含空格,所以要使用getline获取字符串。接着使用rfind查找最后一个空格,从而确定最后一个单词的长度。

#include <iostream>
using namespace std;int main() {string s;getline(cin, s);// 查找最后一个空格size_t ret = s.rfind(' ');if (ret != string::npos)cout << s.size() - ret - 1 << endl;else // 字符串没有空格cout << s.size() << endl;return 0;
}

7.4 验证一个字符串是否是回文 

验证一个字符串是否是回文原题链接https://leetcode.cn/problems/valid-palindrome/description/

定义左指针left和右指针right,两个指针从两边向中间移动,直到相遇为止。若遇到数字或字母,判断它们指向的字符转小写字母后是否相同,从而判断是否是回文串。

class Solution {
public:bool isPalindrome(string s) {int left = 0, right = s.size() - 1;while (left < right){// 左边找数字字母while (left < right && !isalnum(s[left]))++left;// 右边找数字字母while (left < right && !isalnum(s[right]))--right;if (tolower(s[left++]) != tolower(s[right--]))return false;}return true;}
};

7.5 字符串相加 

字符串相加原题链接https://leetcode.cn/problems/add-strings/description/

从最低位开始,每次取对应位的数字相加,并计算进位。考虑把相加的结果插入到字符串头部,但是反复的头插效率太低,所以应把相加的结果尾插到字符串中,再整体反转。注意到插入的过程中可能会多次扩容,所以先用reserve保留足够的空间,避免扩容的消耗。

class Solution {
public:string addStrings(string num1, string num2) {string ret;ret.reserve(max(num1.size(), num2.size()) + 1);int carry = 0; // 进位int end1 = num1.size() - 1, end2 = num2.size() - 1;while (end1 >= 0 || end2 >= 0){// 取出对应位置的数字int n1 = end1 >= 0 ? num1[end1] - '0' : 0;int n2 = end2 >= 0 ? num2[end2] - '0' : 0;int sum = n1 + n2 + carry;carry = sum / 10;ret += ((sum % 10) + '0');--end1;--end2;}if (carry == 1)ret += '1';reverse(ret.begin(), ret.end());return ret;}
};

8. 最简易模拟实现

如果不考虑容量问题,理论上只需要一个char*类型的指针就能管理字符串了。要实现一个最简易的string,只需要实现利用C-string的构造函数、拷贝构造、赋值运算符重载以及析构函数。除此之外,可以顺手实现C++11中的移动构造和移动赋值,以及size、c_str、关系运算符、operator[]等接口。

8.1 C-string构造

利用C-string构造,只需要new出足够大的空间,多开一个空间存储'\0'。

string(const char* s = ""):_str(new char[strlen(s)+1])
{strcpy(_str, s);
}

8.2 拷贝构造

拷贝构造,可以利用C++标准规定的现代写法,转为利用C-string构造。

string(const string& str):string(str._str)
{}

8.3 赋值运算符重载

赋值运算符重载,仍然使用现代写法,采取传值传参。

string& operator=(string tmp)
{swap(tmp);return *this;
}

其中swap交换对应的_str指针即可。

void swap(string& str)
{std::swap(str._str, _str);
}

8.4 析构函数

析构函数直接delete即可。

~string()
{delete[] _str;_str = nullptr;
}

8.5 移动构造和移动赋值

C++11中的移动构造和移动赋值,只需要交换_str指针。

string(string&& str) noexcept:_str(str._str)
{str._str = nullptr;
}string& operator=(string&& tmp) noexcept
{swap(tmp);return *this;
}

8.6 c_str + size + operator[]

接下来c_str、size、operator[]都可以顺手实现。

size_t size() const
{return strlen(_str);
}const char* c_str() const
{return _str;
}char& operator[](size_t pos)
{assert(pos <= size());return _str[pos];
}const char& operator[](size_t pos) const
{assert(pos <= size());return _str[pos];
}

8.7 关系运算符

关系运算符重载作为非成员函数,只需实现>和==,其余复用即可。

bool operator>(const string& str1, const string& str2)
{return strcmp(str1.c_str(), str2.c_str()) > 0;
}bool operator==(const string& str1, const string& str2)
{return strcmp(str1.c_str(), str2.c_str()) == 0;
}bool operator>=(const string& str1, const string& str2)
{return str1 > str2 || str1 == str2;
}bool operator<=(const string& str1, const string& str2)
{return !(str1 > str2);
}bool operator!=(const string& str1, const string& str2)
{return !(str1 == str2);
}bool operator<(const string& str1, const string& str2)
{return !(str1 >= str2);
}

8.8 完整实现

完整的实现及测试代码如下:

#include<iostream>
#include<string.h>
#include<assert.h>
#include<utility>
using namespace std;namespace xbl
{class string{public:string(const char* s = ""):_str(new char[strlen(s)+1]){strcpy(_str, s);}string(const string& str):string(str._str){}string(string&& str) noexcept:_str(str._str){str._str = nullptr;}string& operator=(string tmp){swap(tmp);return *this;}string& operator=(string&& tmp) noexcept{swap(tmp);return *this;}~string(){delete[] _str;_str = nullptr;}size_t size() const{return strlen(_str);}const char* c_str() const{return _str;}char& operator[](size_t pos){assert(pos <= size());return _str[pos];}const char& operator[](size_t pos) const{assert(pos <= size());return _str[pos];}void swap(string& str){std::swap(str._str, _str);}private:char* _str;};bool operator>(const string& str1, const string& str2){return strcmp(str1.c_str(), str2.c_str()) > 0;}bool operator==(const string& str1, const string& str2){return strcmp(str1.c_str(), str2.c_str()) == 0;}bool operator>=(const string& str1, const string& str2){return str1 > str2 || str1 == str2;}bool operator<=(const string& str1, const string& str2){return !(str1 > str2);}bool operator!=(const string& str1, const string& str2){return !(str1 == str2);}bool operator<(const string& str1, const string& str2){return !(str1 >= str2);}void test_string(){string s1;cout << s1.c_str() << endl;string s2("hello world");cout << s2.size() << endl;cout << s2[1] << endl;string s3(s2);string s4 = s3;s1 = s2;cout << s1.c_str() << endl;cout << s2.c_str() << endl;cout << s3.c_str() << endl;cout << s4.c_str() << endl;cout << (s1 == s2) << endl;}
}

输出:


11
e
hello world
hello world
hello world
hello world
1

9. 带size和capacity的版本

最简易模拟实现的思路适用于面试时,被要求模拟实现string。特点是实现简单,但效率不高,因为strlen遍历字符串的时间复杂度是O(N)。一个功能完善的string可以考虑带上size_t类型的_size和_capacity成员变量,分别用来记录有效元素的个数和容量。这个版本也是我重点要讲解的版本。

9.1 C-string构造

这样C-string的构造函数就可以这样写。

string(const char* s = ""):_size(strlen(s))
{_capacity = _size == 0 ? 3 : _size;_str = new char[_capacity + 1];strcpy(_str, s);
}

观察监视窗口:

91f1b515708d478486eb02fcd782f798.png

9.2 拷贝构造

拷贝构造不能简单地利用C-string构造实现了,这是因为下面的情况中,C-string以'\0'结尾,那么'\0'后面的字符就不会被识别为有效字符。

6b95482e1530406f92900ae91afdf61e.png

所以,正确的做法是,使用memcpy,把空间上的有效数据拷贝过去。

string(const string& str):_str(new char[str._size + 1]), _size(str._size), _capacity(str._size)
{memcpy(_str, str._str, (_size + 1) * sizeof(char));
}

观察监视窗口:

a80481bcb64d468bbcab4b5f4125e792.png

9.3 赋值运算符重载

采用现代写法,利用传值传参的深拷贝。

string& operator=(string tmp)
{swap(tmp);return *this;
}void swap(string& str)
{std::swap(_str, str._str);std::swap(_size, str._size);std::swap(_capacity, str._capacity);
}

e4573f6581674220825e877b2936f747.png

9.4 析构函数

释放_str指针指向的空间。

~string()
{delete[] _str;_size = _capacity = 0;
}

9.5 移动构造和移动赋值

交换对应的数据即可。

string(string&& str) noexcept:_str(nullptr)
{swap(str);
}string& operator=(string&& str) noexcept
{swap(str);return *this;
}

9.6 c_str + size + capacity

返回对应的成员变量。

const char* c_str() const
{return _str;
}size_t size() const
{return _size;
}size_t capacity() const
{return _capacity;
}

9.7 operator[]

实现const和非const两个版本。

char& operator[](size_t pos)
{assert(pos < _size);return _str[pos];
}const char& operator[](size_t pos) const
{assert(pos < _size);return _str[pos];
}

9.8 关系运算符

不能直接使用strcmp,因为下面的情况中,如果使用C-string比较,s1和s2应该相等,但事实上,s1<s2,原因是s2更长。

std::string s1("hello world");
std::string s2("hello world");
s2 += '\0';cout << (s1 == s2) << endl;
cout << (s1 < s2) << endl;

输出:

0
1

所以,要手动比较。先考虑operator<,同时遍历两个字符串,若遇到不相等的字符,则比较结束,结果是“小于”或者“大于”,判断是否是“小于”即可。若其中一个字符串遍历完了,那么长的字符串更大。再考虑operator==,当两个字符串的size相同,且所有字符均相同,则字符串相等。其余的关系运算符复用operator<和operator==即可。

bool operator<(const string& str1, const string& str2)
{size_t i = 0;while (i < str1.size() && i < str2.size()){if (str1[i] != str2[i]){return str1[i] < str2[i];}++i;}// 若有效字符均相等,则长的字符串更大return str1.size() < str2.size();
}bool operator==(const string& str1, const string& str2)
{if (str1.size() != str2.size()){return false;}// size相同for (size_t i = 0; i < str1.size(); ++i){if (str1[i] != str2[i]){return false;}}return true;
}bool operator<=(const string& str1, const string& str2)
{return str1 < str2 || str1 == str2;
}bool operator>(const string& str1, const string& str2)
{return !(str1 <= str2);
}bool operator>=(const string& str1, const string& str2)
{return !(str1 < str2);
}bool operator!=(const string& str1, const string& str2)
{return !(str1 == str2);
}

9.9 clear

直接把_size改为0。

void clear()
{_str[0] = '\0';_size = 0;
}

9.10 reserve

如果reserve的参数n>_capacity,需要扩容;如果n<=_capacity,无需处理。

void reserve(size_t n)
{if (n > _capacity){// 扩容+拷贝数据char* tmp = new char[n + 1];memcpy(tmp, _str, _size + 1);_str = tmp;_capacity = n;}
}

9.11 insert

首先,检查容量,空间不够了就用reserve扩容,这里实现为至少扩容2倍。接着,使用memmove挪动数据,空出足够的空间,注意计算挪动数据的个数,要挪动的数据是[pos,_size],包括字符串末尾的'\0',总计_size-pos+1字节。最后,插入数据,可以使用memset或者strncpy。

string& insert(size_t pos, size_t n, char c)
{assert(pos <= _size);if (_size + n > _capacity){// 至少扩2倍reserve(max(_size + n, _capacity * 2));}// 挪动数据memmove(_str + pos + n, _str + pos, (_size - pos + 1) * sizeof(char));// 插入memset(_str + pos, c, n * sizeof(char));_size += n;return *this;
}string& insert(size_t pos, const char* s)
{assert(pos <= _size);size_t len = strlen(s);if (_size + len > _capacity){// 至少扩2倍reserve(max(_size + len, _capacity * 2));}// 挪动数据memmove(_str + pos + len, _str + pos, (_size - pos + 1) * sizeof(char));// 插入strncpy(_str + pos, s, len);_size += len;return *this;
}

在调用insert前后,观察调试窗口:

7e69962fd33047d89fd2b805044cb4af.png

fde34bb6a2cb472b85a9fcce3e872a80.png

e78559b5647e4f3895dd5073e52c1344.png

9.12 push_back + append + operator+=

复用insert即可。

void push_back(char c)
{insert(_size, 1, c);
}void append(const char* s)
{insert(_size, s);
}string& operator+=(char c)
{push_back(c);return *this;
}string& operator+=(const char* s)
{append(s);return *this;
}

在调用operator+=前后,观察调试窗口:

e976348619714af3a9db6025e26880c1.png

239cb3803d0f47978a2798522df3ec37.png

2b81ebd0137b4b3fb45a0081b308f811.png

9.13 empty

判断_size是否为0即可。

bool empty() const
{return _size == 0;
}

9.14 resize

若resize的参数n>_size,先考虑容量是否充足,接着在字符串末尾插入n-_size个字符c。最后在字符串末尾设置'\0'。

void resize(size_t n, char c = '\0')
{if (n > _size){if (n > _capacity){reserve(max(n, _capacity * 2));}// 填充n-size个cmemset(_str + _size, c, (n - _size) * sizeof(char));}_str[n] = '\0';_size = n;
}

分n<_size,n==_size和n>_size三种情况,观察调试窗口:

480af81214344417be1acca462bd8a45.png

5e00a27edd304167b22d3d85391b0e38.png

99ea0d9a31594f5d890f8856e0d690fc.png

81cfd25c7b8b44f991e9de5b0d159251.png

9.15 迭代器

string的迭代器用字符指针即可。begin指向起始位置_str,end指向结束位置_str+_size,即'\0'的位置。

typedef char* iterator;
typedef const char* const const_iterator;iterator begin()
{return _str;
}const_iterator begin() const
{return _str;
}iterator end()
{return _str + _size;
}const_iterator end() const
{return _str + _size;
}

9.16 erase

挪动数据,覆盖删除即可。注意计算挪动数据的个数,要挪动的数据是(pos,_size],不包括pos位置,包括末尾的'\0',总共_size-pos字节。

string& erase(size_t pos = 0)
{assert(pos < _size);// 挪动数据,覆盖删除memmove(_str + pos, _str + pos + 1, (_size - pos) * sizeof(char));--_size;return *this;
}

观察调试窗口:

9bf5f4aef17b49b29e41529c1b8d6372.png

9501061d10224352b015e6fd97a6c20a.png

9.17 find

不能使用strchr和strstr,因为string内存储的字符串可能包含'\0',而C语言的函数遇到'\0'就不会继续查找了。

只能根据strchr和strstr的原理,手动实现。对于字符的查找,从pos位置开始匹配即可。对于字符串的查找,也是从pos位置开始匹配,对于每一个pos,令下标i从pos开始,j从0开始,分别遍历_str[i]和s[j],直到越界或者出现不匹配的字符。

size_t find(char c, size_t pos = 0) const
{if (pos >= _size){return npos;}for (size_t i = pos; i < _size; ++i){if (_str[i] == c){return i;}}return npos;
}size_t find(const char* s, size_t pos = 0) const
{if (pos >= _size){return npos;}size_t i = 0; // _strsize_t j = 0; // swhile (pos < _size){i = pos;j = 0;while (i < _size && s[j] && _str[i] == s[j]){++i;++j;}if (s[j] == '\0'){// 匹配成功return pos;}++pos;}// 匹配失败return npos;
}

9.18 operator<<

使用范围for取出所有字符并插入到流中即可。

ostream& operator<<(ostream& out, const string& str)
{for (auto ch : str){out << ch;}return out;
}

9.19 operator>>

反复使用istream类对象的get,获取字符,并尾插到str中即可。

istream& operator>>(istream& in, string& str)
{str.clear();char ch = '\0';in.get(ch);while (ch != ' ' && ch != '\n'){str.push_back(ch);in.get(ch);}return in;
}

但是上面的写法会导致频繁的扩容。建议定义一个字符数组buff,每次读取字符后先放到buff中,buff放满了再存储到str中。

istream& operator>>(istream& in, string& str)
{str.clear();char buff[128] = { 0 };char ch = '\0';in.get(ch);size_t i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;// buff放满了再添加到str中if (i == 127){buff[i] = '\0';str.append(buff);i = 0;}in.get(ch);}buff[i] = '\0';str += buff;return in;
}

9.20 完整实现

至此,string类的常用接口都实现完了!下面附上完整的实现及测试代码,供读者调试。

#include<iostream>
#include<string>
#include<algorithm>
#include<assert.h>
using namespace std;namespace xbl
{class string{public:typedef char* iterator;typedef const char* const const_iterator;public: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* s = ""):_size(strlen(s)){_capacity = _size == 0 ? 3 : _size;_str = new char[_capacity + 1];strcpy(_str, s);}string(const string& str):_str(new char[str._size+1]), _size(str._size), _capacity(str._size){memcpy(_str, str._str, (_size + 1) * sizeof(char));}string& operator=(string tmp){swap(tmp);return *this;}~string(){delete[] _str;_size = _capacity = 0;}string(string&& str) noexcept:_str(nullptr){swap(str);}string& operator=(string&& str) noexcept{swap(str);return *this;}void clear(){_str[0] = '\0';_size = 0;}void reserve(size_t n){if (n > _capacity){// 扩容+拷贝数据char* tmp = new char[n + 1];memcpy(tmp, _str, _size + 1);_str = tmp;_capacity = n;}}void resize(size_t n, char c = '\0'){if (n > _size){if (n > _capacity){reserve(max(n, _capacity * 2));}// 填充n-size个cmemset(_str + _size, c, (n - _size) * sizeof(char));}_str[n] = '\0';_size = n;}void push_back(char c){insert(_size, 1, c);}void append(const char* s){insert(_size, s);}string& operator+=(char c){push_back(c);return *this;}string& operator+=(const char* s){append(s);return *this;}string& insert(size_t pos, size_t n, char c){assert(pos <= _size);if (_size + n > _capacity){// 至少扩2倍reserve(max(_size + n, _capacity * 2));}// 挪动数据memmove(_str + pos + n, _str + pos, (_size - pos + 1) * sizeof(char));// 插入memset(_str + pos, c, n * sizeof(char));_size += n;return *this;}string& insert(size_t pos, const char* s){assert(pos <= _size);size_t len = strlen(s);if (_size + len > _capacity){// 至少扩2倍reserve(max(_size + len, _capacity * 2));}// 挪动数据memmove(_str + pos + len, _str + pos, (_size - pos + 1) * sizeof(char));// 插入strncpy(_str + pos, s, len);_size += len;return *this;}string& erase(size_t pos = 0){assert(pos < _size);// 挪动数据,覆盖删除memmove(_str + pos, _str + pos + 1, (_size - pos) * sizeof(char));--_size;return *this;}void swap(string& str){std::swap(_str, str._str);std::swap(_size, str._size);std::swap(_capacity, str._capacity);}const char* c_str() const{return _str;}size_t size() const{return _size;}size_t capacity() const{return _capacity;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}bool empty() const{return _size == 0;}size_t find(char c, size_t pos = 0) const{if (pos >= _size){return npos;}for (size_t i = pos; i < _size; ++i){if (_str[i] == c){return i;}}return npos;}size_t find(const char* s, size_t pos = 0) const{if (pos >= _size){return npos;}size_t i = 0; // _strsize_t j = 0; // swhile (pos < _size){i = pos;j = 0;while (i < _size && s[j] && _str[i] == s[j]){++i;++j;}if (s[j] == '\0'){// 匹配成功return pos;}++pos;}// 匹配失败return npos;}public:static const size_t npos;private:char* _str;size_t _size;size_t _capacity;};const size_t string::npos = -1;bool operator<(const string& str1, const string& str2){size_t i = 0;while (i < str1.size() && i < str2.size()){if (str1[i] != str2[i]){return str1[i] < str2[i];}++i;}// 若有效字符均相等,则长的字符串更大return str1.size() < str2.size();}bool operator==(const string& str1, const string& str2){if (str1.size() != str2.size()){return false;}// size相同for (size_t i = 0; i < str1.size(); ++i){if (str1[i] != str2[i]){return false;}}return true;}bool operator<=(const string& str1, const string& str2){return str1 < str2 || str1 == str2;}bool operator>(const string& str1, const string& str2){return !(str1 <= str2);}bool operator>=(const string& str1, const string& str2){return !(str1 < str2);}bool operator!=(const string& str1, const string& str2){return !(str1 == str2);}ostream& operator<<(ostream& out, const string& str){for (auto ch : str){out << ch;}return out;}istream& operator>>(istream& in, string& str){str.clear();char buff[128] = { 0 };char ch = '\0';in.get(ch);size_t i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;// buff放满了再添加到str中if (i == 127){buff[i] = '\0';str.append(buff);i = 0;}in.get(ch);}buff[i] = '\0';str += buff;return in;}void test_string(){string s1("hello world");cout << s1.c_str() << endl;string s2(s1);cout << s2.c_str() << endl;string s4 = s1;cout << s4.c_str() << endl;string s3("test");s3 = s1;cout << s3.c_str() << endl;for (size_t i = 0; i < s3.size(); ++i){cout << s3[i] << " ";}cout << endl;string::iterator it = s3.begin();while (it != s3.end()){cout << *it << " ";++it;}cout << endl;for (auto ch : s3){cout << ch << " ";}cout << endl;cout << (s1 == s3) << endl;cout << (s1 > "hello") << endl;cout << (s1 < "z") << endl;s3.insert(5, 3, 'x');cout << s3.c_str() << endl;s3.insert(6, "test");cout << s3.c_str() << endl;s2 += '!';cout << s2.c_str() << endl;s2 += "test";cout << s2.c_str() << endl;// n==sizes1.resize(11);// n<sizes1.resize(5);// n>sizes1.resize(8, 'x');s4.erase(5);cout << s4.c_str() << endl;string s5("abcabbcabbbcabbbbc");size_t ret = s5.find('c');if (ret != string::npos){cout << ret << endl;}else{cout << "没找到" << endl;}ret = s5.find("bbbc");if (ret != string::npos){cout << ret << endl;}else{cout << "没找到" << endl;}cin >> s4 >> s5;cout << s4 << endl;cout << s5 << endl;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/684527.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Leetcode-103. 二叉树的锯齿形层序遍历

这个年和树过不去啦啦啦&#xff01; 题目&#xff1a; 给你二叉树的根节点 root &#xff0c;返回其节点值的 锯齿形层序遍历 。&#xff08;即先从左往右&#xff0c;再从右往左进行下一层遍历&#xff0c;以此类推&#xff0c;层与层之间交替进行&#xff09;。 示例 1&…

Linux设置jar包开机自启动

步骤 1、新建jar包自启文件 sudo vi /etc/init.d/jarSysInit.sh 按i键进入编辑模式输入以下内容&#xff1a; export JAVA_HOME/home/jdk/jdk-11.0.22 export CLASSPATH.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/jre/lib/rt.jar export PATH$PATH:$JAVA_…

跟着pink老师前端入门教程-day26

一、计算机编程基础 &#xff08;一&#xff09;编程语言 1、编程 编程&#xff1a;就是让计算机为解决某个问题而使用某种程序设计语言编写程序代码&#xff0c;并最终得到结果的过程。 计算机程序&#xff1a;就是计算机所执行的一系列的指令集合&#xff0c;而程序全部…

二叉搜索树题目:递增顺序搜索树

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 解法三思路和算法代码复杂度分析 后记 题目 标题和出处 标题&#xff1a;递增顺序搜索树 出处&#xff1a;897. 递增顺序搜索树 难度 3 级 题目描述…

P1498 南蛮图腾题解

题目 给定一个正整数n&#xff0c;参考输出样例&#xff0c;输出图形。 输入输出格式 输入格式 每个数据输入一个正整数n&#xff0c;表示图腾的大小&#xff08;此大小非彼大小&#xff09; 输出格式 这个大小的图腾 输入输出样例 输入样例 2 输出样例 /\/__\/\ /\…

HTTP缓存技术

大家好我是苏麟 , 今天说说HTTP缓存技术 . 资料来源 : 小林coding 小林官方网站 : 小林coding (xiaolincoding.com) HTTP缓存技术 HTTP 缓存有哪些实现方式? 对于一些具有重复性的 HTTP 请求&#xff0c;比如每次请求得到的数据都一样的&#xff0c;我们可以把这对「请求-响…

【mysql】数据约束

一、数据约束&#xff1a; 什么是约束&#xff1f; 为了确保表中的数据的完整性(准确性、正确性)&#xff0c;为表添加一些限制。是数据库中表设计的一个最基本规则。使用约束可以使数据更加准确&#xff0c;从而减少冗余数据&#xff08;脏数据&#xff09;。 数据库完整性约…

84 CTF夺旗-PHP弱类型异或取反序列化RCE

目录 案例1&#xff1a;PHP-相关总结知识点-后期复现案例2&#xff1a;PHP-弱类型对比绕过测试-常考点案例3&#xff1a;PHP-正则preg_match绕过-常考点案例4&#xff1a;PHP-命令执行RCE变异绕过-常考点案例5&#xff1a;PHP-反序列化考题分析构造复现-常考点涉及资源&#xf…

【HTML】过年不能放烟花,那就放电子烟花

闲谈 大家回家过年可能都多多少少放过些&#x1f9e8;&#xff0c;但是有些在城市上过年的小伙伴可能就没有机会放鞭炮了。不过没关系&#xff0c;我们懂技术&#xff0c;我们用技术自娱自乐&#xff0c;放电子烟花&#xff0c;总不可能被警长叔叔敲门问候吧。 开干 首先&…

Redis篇----第一篇

系列文章目录 文章目录 系列文章目录前言一、什么是 Redis?二、Redis 与其他 key-value 存储有什么不同?三、Redis 的数据类型?四、使用 Redis 有哪些好处?五、Redis 相比 Memcached 有哪些优势?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住…

Spring 用法学习总结(四)之 JdbcTemplate 连接数据库

&#x1f409;目录 9 JdbcTemplate 9 JdbcTemplate Spring 框架对 JDBC 进行了封装&#xff0c;使用 JdbcTemplate 方便实现对数据库操作 相关包&#xff1a; 百度网盘链接https://pan.baidu.com/s/1Gw1l6VKc-p4gdqDyD626cg?pwd6666 创建properties配置文件 &#x1f4a5;注意…

分布式文件系统 SpringBoot+FastDFS+Vue.js【二】

分布式文件系统 SpringBootFastDFSVue.js【二】 六、实现上传功能并展示数据6.1.创建数据库6.2.创建spring boot项目fastDFS-java6.3.引入依赖6.3.fastdfs-client配置文件6.4.跨域配置GlobalCrosConfig.java6.5.创建模型--实体类6.5.1.FastDfsFile.java6.5.2.FastDfsFileType.j…

vscode 和 keil协同使用开发stm32程序,超详细教程

vscode 和 keil协同使用开发stm32程序 文章目录 vscode 和 keil协同使用开发stm32程序1. 安装vscode拓展安装chinese插件 2 .安装Mingw3.配置环境变量4. 打开Keil项目 VSCODE 是一款广受好评的代码编辑器&#xff0c; KEIL 是常用的嵌入式开发工具但编程界面简陋。 将两个工具…

npm使用国内淘宝镜像(最新地址)

目录 前言 一、命令配置 二、使用cnpm安装 三、常见包地址 四、总结 往期回顾 前言 我们前端程序员在使用国外的镜像源速度很慢并且容易下载失败&#xff0c;有时候需要尝试多次才有可能下载成功&#xff0c;很麻烦&#xff0c;但是可以切换为国内镜像源&#xff0c;下…

BigDecimal的常用API

BigDecimal用于解决浮点型运算时结果出现失真的问题。 这里0.20.1等于0.3就出现了失真 import java.math.BigDecimal; import java.math.RoundingMode;public class Test {public static void main(String[] args) {//BigDeciaml的使用&#xff1a;解决小数运算失真的问题doub…

微信网页版能够使用(会顶掉微信app的登陆)

一、文件结构 新建目录chrome新建icons&#xff0c;其中图片你自己找吧新建文件manifest.json新建文件wx-rules.json 二、文件内容 对应的png你们自己改下 1、manifest.json {"manifest_version": 3,"name": "wechat-need-web","author…

C语言第二十五弹---字符函数和字符串函数(上)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 目录 1、字符分类函数 2、字符转换函数 3、strlen的使用和模拟实现 4、strcpy 的模拟实现 5、strcat 的模拟实现 6、strcmp 的模拟实现 7、strncpy 函数的使用 总结…

Excel练习:日历

Excel练习&#xff1a;日历 ‍ 题目&#xff1a;制作日历 ‍ ​​ 用rows和columns函数计算日期单元格偏移量 一个公式填充所有日期单元格 ​​ ‍

CSP-201909-1-小明种苹果

CSP-201909-1-小明种苹果 #include <iostream> using namespace std; int main() {long long sumApple 0, maxNum 0, maxAppleNum 0, n, m;cin >> n >> m;for (long long i 0; i < n; i){long long appleNum, delta 0;cin >> appleNum;for (l…

【JavaEE】spring boot快速上手

SpringBoot快速上手 文章目录 SpringBoot快速上手Maven会出现的一个官方bug创建完项目之后常用的的三个功能依赖管理Maven仓库中央仓库本地仓库国内源配置私服 springboot项目创建什么是springspring boot项目的创建Hello Worldweb服务器 SpringMVC什么是SpringWebMVC什么是MVC…