文章目录
1. string
类的介绍
C++ string类模板
在使用 string
类时,必须包含 #include<string>头文件
以及 using namespace std;
2. 常用操作
2.1 string
的定义方式 (构造)
string();//构造空字符串
string(const char*s);//用C-string来构造string类对象
string(size_t n,char c);//string类对象中包含n个字符c
string(const string& s);//拷贝构造函数
使用示例:
int main()
{
string s1;//无参,构造空的string
string s2("hello world");//字符串
string s3 = s2;//注意这不是赋值,而是拷贝构造
string s4(s2);//和上一条代码等价,都是拷贝构造
string s4(10, 's'); //生成10个's'字符的字符串
}
2. 2 string
的容量操作
1 . 使用 size()
函数或者 length()
函数获取当前有效字符的个数
`size_t size() const;`
`size_t size() const; `
using namespace std;
#include <iostream>
#include <string>
int main()
{
std::string str("AmAo");
std::cout << str.size() << endl;//4
std::cout << str.length()<<endl;//4
return 0;
}
size()
与length()
方法底层实现原理完全相同,引入size()
的原因是为了与其他容器的接口保持一 致,一般情况下基本都是用size()
。
2 . capacity()
返回空间总大小
无论是 size
还是 capacity
都不包含 \0
。
size
代表字符串有效长度
capacity
代表字符串的实际长度
3 . empty()
:是空字符串就返回 true
,否则返回 false
4 . reserve(size_t n)
: 为字符串预留空间
如果调整的容量小于已有空间容量,则容量不会减小。
5 . resize(size_t n,char c)
1. 当n大于对象当前的size时,将size扩大到n,多余的字符空间用 c 来填充,若c未给出,则默认为 '\0'。
2. 当n小于对象当前的size时,将size缩小到n。
6 . clear()
: 清空(有效)字符串
- clear函数只将size清零
- clear函数不会改变capacity
2.3 string
的遍历和访问
1 . operator[]
返回 pos 位置的字符。跟以前的 []
使用方法一致。
string s1("hello world");
//第一种遍历
//[下标]
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i] << " ";
}
cout << endl;
s1[0] = 'x';
cout << s1 << endl;
2 . begin()
和 end()
两个都是迭代器,暂时当作指针理解,后续会深入学习迭代器。
begin()
:获取第一个字符的迭代器;end()
:获取**最后一个字符的下一个位置(一般是\0
)**的迭代器。
//第二种遍历:迭代器
//迭代器像指针(但不是指针),对于数据结构的遍历,迭代器更方便
string s1("hello world");
string::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it << " ";
++it;
}
cout << endl;
3 . rbegin()
和 rend()
相比于上面的两个,这两个是逆向迭代器。逆向迭代器允许你以与正向迭代器相反的顺序遍历容器中的元素,即从后向前遍历。
rbegin()
:
- 返回一个迭代器,它指向容器的最后一个元素的前一个位置(即,如果容器非空,指向最后一个元素的逆向迭代器)。
- 如果容器为空,
rbegin()
返回的迭代器等同于rend()
返回的迭代器。
rend()
: - 返回一个迭代器,它指向容器的第一个元素前面的位置(即,逆向迭代器的末尾)。
- 这个迭代器表示逆向遍历的结束。
#include<iostream>
using namespace std;
//逆向迭代器
void func(const string& s)
{
string::const_reverse_iterator it = s.rbegin();
while (it != s.rend())
{
cout << *it << " ";
++it;
}
cout << endl;
}
int main()
{
string s1("hello world");
string::reverse_iterator it1 = s1.rbegin();
func(s1);//d l r o w o l l e h
while (it1 != s1.rend())
{
*it1 = 'x';
cout << *it1 << " ";
++it1;
}
cout << endl;//x x x x x x x x x x x
return 0;
}
4. 范围for
若是需要通过范围for修改对象的元素,则用于接收元素的变量e的类型必须是引用类型,否则e只是对象元素的拷贝,对e的修改不会影响到对象的元素。
string s("CSDN");
//使用范围for访问对象元素
for (auto e : s)
{
cout << e;
}
cout << endl; //CSDN
//使用范围for访问对象元素,并对其进行修改
for (auto& e : s) //需要修改对象的元素,e必须是引用类型
{
e = 'x';
}
cout << s << endl; //xxxx
注意:范围 for
本质就是迭代器。
2.4 string
的修改操作
1. push_back
void push_back (char c);
在字符串后尾插字符 c
2. append
string& append (const string& str);
string& append (const char* s);
在字符串后追加一个字符串
3. operator+=
(最常用)
string& operator+= (const string& str);
string& operator+= (const char* s);
string& operator+= (char c);
在字符串后追加字符串 str
1~3 的使用示例
void test6()
{
string s1 = "hell";
s1.push_back('o');
cout << s1 << endl;//hello
s1.append(" world");
cout << s1 << endl;//hello world
s1 += "!!!";
cout << s1 << endl;//hello world!!!
}
int main()
{
test6();
return 0;
}
小结:
- 在
string
尾部追加字符时,上面三种方法的实现都差不多。一般情况下,string
类的+=
操作用的比较多,+=
操作不仅可以连接单个字符,还可以连接字符串。 - 对
string
操作时,如果能够大概预估到放多少字符,可以先通过reserve
把空间预留好。
4. insert
插入字符和插入字符串
常用的
string& insert (size_t pos, const string& str);
string& insert (size_t pos, const char* s);
string& insert (size_t pos, size_t n, char c);
string s1("hello world");
s1.insert(5, "xxxx");//在下标为5的位置,插入字符串
cout << s1 << endl;//helloxxxx world
s1.insert(0, 1, 'x');//在下标为0的位置插入 1 个'x'
cout << s1 << endl;//xhelloxxxx world
s1.insert(s1.begin(), 'y');//迭代器的使用
cout << s1 << endl;//yxhelloxxxx world
5. c_str
返回 c 格式字符串的指针
c_str函数返回字符串从\0
结尾的字符串,但是c++中的字符串不一定以\0
结尾,c++中字符串类以size
为准来结尾
void func()
{
string filename("test.cpp");
cout << filename << endl;//test.cpp
cout << filename.c_str() << endl;//test.cpp
filename += '\0';
filename += "string.cpp";
cout << filename << endl; // string 对象size为准
//test.cppstring.cpp
cout << filename.c_str() << endl; // 常量字符串对象\0
//test.cpp
}
这段代码反映了 C++ std::string
和 C 风格字符串在处理空字符 '\0'
方面的不同行为。在 C++ 字符串中,空字符只是一个普通字符,而在 C 风格字符串中,空字符标志着字符串的结束。这也展示了 std::string::c_str()
成员函数的用途,它可以提供一个用于与 C 语言兼容的字符数组指针,但需要注意其返回的字符串会在第一个空字符处结束。
6. find
和 npos
size_t find (const string& str, size_t pos = 0) const;
size_t pos = 0
,不加参数默认为 0,参数可以是其他。
`npos`是int的最大值
从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
string s1("hello world hello AmAo");
cout << s1 << endl;
//所有空格替换为20%
size_t pos = s1.find(' ', 0);
while (pos != string::npos)//找不到,才会返回npos
{
s1.replace(pos, 1, "20%");
pos = s1.find(' ', pos + 3);//这样每次就可以不用从头找起
}
cout << s1 << endl;//hello20%world20%hello20%AmAo
改进:
string s2("hello world hello AmAo");
cout << s2 << endl;
string s3;
for(auto ch : s2)
{
if (ch == ' ')
s3 += '20%';
else
s3 += ch;
}
cout << s3 << endl;
//如果需要s2
s2.swap(s3);
cout << s2 << endl;
7. rfind
从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
8. substr
size_t rfind (const string& str, size_t pos = npos) const;
size_t rfind (const char* s, size_t pos = npos) const;
左闭右闭,少一,要加 1
左闭右开,就是个数
//提取子字符串
string str("https://legacy.cplusplus.com/reference/string/string/substr/");
string sub1, sub2, sub3;
size_t pos1 = str.find(':');
sub1 = str.substr(0, pos1 - 0);
cout << sub1 << endl;
size_t pos2 = str.find('/', pos1 + 3);
sub2 = str.substr(pos1 + 3, pos2 - (pos1 + 3));
cout << sub2 << endl;
sub3 = str.substr(pos2 + 1);
cout << sub3 << endl;
结果:
https
legacy.cplusplus.com
reference/string/string/substr/
9. 其他
erase
删除
string& erase (size_t pos = 0, size_t len = npos);
replace
替换
string& replace (size_t pos, size_t len, const string& str);
string s1("hello world");
s1.replace(5, 1, "%20");
cout << s1 << endl;//hello%20world
replace
效率很低,能不用就不用。
2.5 非成员类函数
1. operator+
- operator+能实现简单的字符串拼接,包括字符、字符串、string对象。
【注意】
operator+的底层实现是传值返回,效率较低。
2. operator>>、operator<<
将字符串读取或写入内存中。
3. 习题
字符串中的第一个唯一字符
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 (count[s[i] - 'a'] == 1)
return i;
}
return -1;
}
};
把字符串转换成整数 (atoi)
思路:
排除空格->敲定正负号->转变数字,
遇到第一个英文字符就截断
细节处理就是,别越界了。
数字字符:
- 字符转数字:此数字的
ASCII
码 - 字符‘0’的ASCII
码 - 数字拼接:若从左向右遍历数字,设当前位字符为
c
,当前位数字为x
,数字结果为ret
,则数字拼接公式为:
r e t = 10 ∗ r e t + x ret = 10 * ret + x ret=10∗ret+x
x = a s c i i ( c ) − a s c i i ( ′ 0 ′ ) x = ascii(c) - ascii('0') x=ascii(c)−ascii(′0′)
class Solution {
public:
int myAtoi(string str) {
int i = 0;//下标
int sign = 1, length = str.size();
int ret = 0, bndry = INT_MAX / 10;
//考虑空字符的情况
if (length == 0)
return 0;
//忽略空格
while (str[i] == ' ')
{
i++;
if (i == length)//考虑全是空格的情况
return 0;
}
//判断符号位
if (str[i] == '-')//跳过该字符,并且不要忘记移动指针
{
i++;
sign = -1;
}
else if (str[i] == '+')
{
i++;
sign = 1;
}
//此处有个细节,居然是判断符号只需要一次,“+-2”这种也要求是0
//所以下一个元素是‘-’时,就会退出返回结果0
for (int j = i; j < length; j++)
{
if (str[j] > '9' || str[j] < '0')//遇到非数字字符截断
{
break;
}
//先判断是否越界
if (ret > bndry || (ret == bndry && str[j] > '7'))
return sign == 1 ? INT_MAX : INT_MIN;
//再转变数字
if (str[j] >= '0' && str[j] <= '9')
{
ret = ret * 10 + (str[j] - '0');
}
}
return ret * sign;
}
};
字符串最后一个单词
这道题贼坑的点在于 cin
遇到空格就会分割,比如 A MAO
,他就会分成两个字符串,程序在读取时,也是按照两个字符串来读。
这时候就需要 getline
函数
#include <iostream>
#include <string>
using namespace std;
int main() {
string str;
getline(cin, str);
size_t pos = str.rfind(' ');
if (pos != string::npos)
{
//左开右开
cout << str.size() - pos - 1 << endl;
}
else
{
cout << str.size() << endl;
}
}
验证回文串
class Solution {
public:
bool isPalindrome(string s) {
/*思路:
1.去除空格和非英文数字——难绷这一步就不会写
2.形成新字符串,逆置判断是否相等。
*/
string newString;
for (char ch : s) //去除空格和非英文数字
{
if (isalnum(ch))
{
newString += tolower(ch);
}
}
string newString_reverse(newString.rbegin(), newString.rend());//构造一个newString的逆置字符串
return newString == newString_reverse;
}
};
反转字符串
法一:
class Solution {
public:
void reverseString(vector<char>& s) {
int n = s.size();
int begin = 0, end = n - 1;
while (begin < end)
{
swap(s[begin], s[end]);
begin++;
end--;
}
}
};
法二:
class Solution {
public:
void reverseString(vector<char>& s) {
int n = s.size();
for (int i = 0; i < n / 2; i++) {
swap(s[i], s[n - i - 1]);
}
}
};
反转字符串 II
好家伙,这题目还真看不懂
每隔k个反转k个,末尾不够k个时全部反转;
class Solution {
public:
string reverseStr(string s, int k) {
for (int i = 0; i < s.size(); i += 2 * k)//每次更新索引i的起点,然后进入逆置
{
//如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。
if (i + k <= s.size())
{
reverse(s.begin() + i, s.begin() + i + k);
continue;
}
//如果剩余字符少于 k 个,则将剩余字符全部反转。
//这时候已经是末端字符串
reverse(s.begin() + i, s.end());
}
return s;
}
};
reverse
:逆置
虽然不是 string 类里的,库函数一般都是左闭右开
反转字符串中的单词 III
解法一:原地解法
思路 :在原字符串上进行修改,以空格为逆置终点。
class Solution {
public:
string reverseWords(string s) {
int size = s.size();
int i = 0;
while (i < size)
{
int start = i;
//将i移动到下一个空格的位置
//最后i会停止'\0'
while (i < size && s[i] != ' ')
{
i++;
}
reverse(s.begin() + start, s.begin() + i);
if (s[i] != '\0')//将i移动后一个,到达新的start位置
{
i++;
}
}
return s;
}
};
解法二:使用
思路:以空格为起点,倒过来插入新的字符串
class Solution {
public:
string reverseWords(string s) {
string ret;
int size = s.size();
int i = 0;
while (i < size)
{
int start = i;
//将i移动到下一个空格的位置
//最后i会停止'\0'
while (i < size && s[i] != ' ')
{
i++;
}
int tail = i - 1;//每个while只需要初始化一次
for (int p = 0; p < i - start; p++)//针对非空字符
//这里的p要控制好
{
ret.push_back(s[tail--]);
}
while (i < size && s[i] == ' ')//遇到空格
{
i++;
ret.push_back(' ');
}
}
return ret;
}
};
字符串相加
class Solution {
public:
string addStrings(string num1, string num2) {
int end1 = num1.size() - 1, end2 = num2.size() - 1;
//初始化两个索引变量 `end1` 和 `end2`,它们分别指向 `num1` 和 `num2` 的最后一个字符的位置。
string retStr;//用来存储整理过的字符串
int next = 0;//进位用的
//让长的一方走完才跳出上下相加
while (end1 >= 0 || end2 >= 0)
{
//因为num1和num2都是字符,
//所以我们要对一位上的字符转换成整型才得以进行相加
int val1 = 0, val2 = 0;
//防止下标越界
if (end1 >= 0)
val1 = num1[end1--] - '0';
if (end2 >= 0)
val2 = num2[end2--] - '0';
int ret = val1 + val2 + next;
//考虑到进位
next = ret / 10;
ret %= 10;
retStr += ('0' + ret);//尾插
}
if (next == 1)// 1+9得10 的 “1”
{
retStr += "1";
}
//逆置
reverse(retStr.begin(), retStr.end());
return retStr;
}
};
字符串相乘
class Solution {
public:
string multiply(string num1, string num2) {
int len1 = num1.size(), len2 = num2.size();
int len = len1 + len2;//n位数乘以n位数,最多会是2n位数
//一定要初始化新串的值,否则后面会出现随机值
string ret(len, '0');
//不会出现越界,是根据num1的一位数跟num2的每一位数相乘
for (int i = len1 - 1; i >= 0; i--)
{
//len - 1是数组的最后一位索引
for (int j = len2 - 1; j >= 0; j--)
{
//tmp是当前位之和
int tmp = (num1[i] - '0') * (num2[j] - '0') + (ret[i + j + 1] - '0');
//ret[i + j + 1]是当前结果的最后一位
//因为num1和num2都有[0]
//当前的最后一位,得
ret[i + j + 1] = tmp % 10 + '0';
ret[i + j] += tmp / 10;
}
}
for (int i = 0; i < len; i++)
{
//跳过“0”
if (ret[i] != '0')
return ret.substr(i);
}
return "0";
}
};
4. 模拟实现——是为了更好地理解
4.1 string
类接口总览(即将模拟实现)
namespace AmAo
{
class string
{
public:
//不是所有的迭代器都是原生指针
//底层是连续的空间,才可以如此
typedef char* iterator;
typedef const char* const_iterator;
//默认成员函数
string(const char* str = "");//构造函数
string(const string& s);//拷贝构造函数
string& operator=(string s);//赋值运算符重载函数
~string();//析构函数
//迭代器相关函数
iterator begin();
iterator end();
const_iterator begin()const;
const_iterator end()const;
容量和大小相关函数
size_t size()const;
void reserve(size_t n);
//访问字符串相关函数
const char& operator[](size_t pos)const;//只读不写
char& operator[](size_t pos);//可读可写
//修改字符串相关函数
string& operator+=(char ch);
string& operator+=(const char* str);
const char* c_str()const;
void push_back(char ch);
void append(const char* str);
void insert(size_t pos, char ch);//插入单个字符
void insert(size_t pos, const char* str);
size_t find(char ch, size_t pos = 0);
size_t find(const char* str, size_t pos = 0);
void erase(size_t pos, size_t len = npos);
void swap(string& s);
string substr(size_t pos = 0, size_t len = npos);
void clear();
ostream& operator<<(ostream& out, const string& s);
istream& operator>>(istream& in, string& s);
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
const static size_t npos = -1;
};
4.2 成员变量
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
const static size_t npos = -1;
字符数组: char* _str = nullptr;
大小(记录有效字符个数):size_t _size = 0;
容量:size_t _capacity = 0;
const static size_t npos = -1;
表示未找到,主要用于“搜索函数的返回值”和“无效位置的表示”。
它的值通常被实现为一个最大的无符号整数值,以确保它总是大于任何有效的字符串索引。
4.3 默认成员函数
4.3.1 构造函数
string(const char* str = '' )
{
_size = strlen(str);//字符串大小设置为str的字符串长度
_capacity = _size;//字符串容量也设置为str的字符串长度
_str = new char[_capacity + 1];//capacity是有效容量。这里+1是为了'\0'
strcpy(_str, str);//将str的C字符串赋值给新开的_str
}
构造函数设置为缺省参数,若不传参数,则默认构造为空字符串。
内置string默认空串为\0
所传参数类型是 const char* str
,是因为一个字符串字面量(如: "Hello world!"
)的类型就是 const char[]
.
4.3.2 拷贝构造函数
首先了解一下深浅拷贝。
浅拷贝与深拷贝
String
类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用 s1
构造 s2
时,编译器会调用默认的拷贝构造。最终导致的问题是,s1
、s2
共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。
浅拷贝:
拷贝出来的目标对象的指针和源对象的指针指向的内存空间是同一块空间。其中一个对象的改动会对另一个对象造成影响。
也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进行操作时,就会发生发生了访问违规。
深拷贝:
源对象与拷贝对象互相独立。其中任何一个对象的改动不会对另外一个对象造成影响。
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。
即给每个对象独立分配资源,保证多个对象之间不会因共享资源而造成多次释放造成程序崩溃问题。
所以在这里,我们要使用深拷贝。
- 传统写法
额外开辟一块空间==(该空间可以容纳内容的大小)==,然后将内容拷贝
//s2(s1)
string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
- 现代写法
先用构造函数,再交换函数内容。
swap
函数,后面也会用到。
void string::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 tmp(s._str);
//调用构造函数,构造出一个C字符串为s._str的对象
swap(tmp);
}
4.3.3 赋值运算符重载函数
- 传统写法
与拷贝构造函数类似,不同在要判断是否是给自己赋值(如果自己给自己赋值,则无需操作),以及在开辟给新字符串足够的空间之前,要先将原来的空间释放掉(释放掉的还有内容)
//s1 = s3 拷贝构造
string& operator=(const 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;
}
- 现代写法
注意:这里的形参s
是一个临时对象(不是引用),不影响s3
,离开了函数域自然会被销毁。
//s1 = s3 拷贝构造
string& operator=(string s) //编译器接收右值的时候自动调用拷贝构造函数
{
swap(s); //交换这两个对象
return *this; //返回左值(支持连续赋值)
}
4.3.4 析构函数
//析构函数
~string()
{
delete[] _str; //释放_str指向的空间
_str = nullptr; //及时置空,防止非法访问
_size = 0; //大小置0
_capacity = 0; //容量置0
}
4.4 迭代器相关函数
begin
和 end
iterator begin()
{
return _str;
}
iterator end()//返回的是有效长度的下一个
{
return _str + _size;
}
const_iterator begin()const
{
return _str;
}
const_iterator end()const
{
return _str + _size;
}
4.5 容量和大小相关函数
size
和 capacity
//大小,获取字符串的有效长度,不包含'\0'
size_t size()const
{
return _size; //返回字符串当前的有效长度
}
//容量,获取该字符串当前的容量
size_t capacity()const
{
return _capacity; //返回字符串当前的容量
}
reserve
- 为字符串预留空间。如果调整的容量小于已有空间容量,则容量不会减小。
void reserve(size_t n)
//功能:开辟n个空间。
//为了在原大小的字符串“留出”空间,但字符串不再是原来空间。
{
if (n > _capacity)
{
char* tmp = new char[n + 1];//多一个'\0'
strcpy(tmp, _str);
delete[]_str;//删去旧空间
_str = tmp;//指向新空间
_capacity = n;
}
}
4.6 访问字符串相关函数
operator[]
const char& string::operator[](size_t pos)const//加了const——只读不写
{
assert(pos <= _size);//等于就是'\0'
return _str[pos];
}
char& string::operator[](size_t pos)//返回引用——可读可写
{
assert(pos <= _size);//等于就是'\0'
return _str[pos];
}
4.7 修改字符串相关函数
c_str
const char* c_str() const
{
return _str;
}
+=
、push_back
、append
、insert
void string::push_back(char ch)//插入字符
{
if (_size == _capacity)//容量满了
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_size + _str, str);
_size += len;
}
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
void string::insert(size_t pos, char ch)//插入单个字符
{
assert(pos <= _size);
if (_size == _capacity)
{
size_t newCapacity = _capacity * 2;
reserve(newCapacity);
}
这里有个易错点,pos是无符号
在下标为0的位置上插入的时候,会死循环
//写法一
//int end = _size;
//while (end >= (int)pos)
//
//{
// _str[end + 1] = _str[end];
// end--;
//}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
_size++;
}
void string::insert(size_t pos, const char* str)//插入字符串
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
int end = _size;
while (end >= (int)pos)//腾出空位
{
_str[end + len] = _str[end];
end--;
}
strncpy(_str + pos, str, len);
//拷贝不带“\0”的字符串填充空位
_size += len;
}
find
size_t string::find(char ch, size_t pos)
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;//找不到就返回npos
}
size_t string::find(const char* str, size_t pos)
{
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
clear
和 erase
void clear()
{
_str[0] = '\0';
_size = 0;
_capacity = 0;
}
void erase(size_t pos)
{
assert(pos >= 0);
_str[pos] = '\0';
_size = pos;
}
operator<<
和 operator>>
ostream& operator<<(ostream& out, const string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();//s里可能已经有内容,+=不会覆盖原有内容
char buff[128];//为了切合开辟的空间,且能够输入特别多的数字而不中断
//buffer!!!就是用来暂存的,像蓄水池
char ch = in.get();//get()才会读取 ' '和'\n'
int i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 127)
{
buff[i] = '\0';
s += buff;
i = 0;//太多时,先加给s,再从头开始走
}
ch = in.get();
}
if (i > 0)//当上述的i未满,会来到这里插入。
{
buff[i] = '\0';
s += buff;
}
return in;
}