前言
作者:小蜗牛向前冲
名言:我可以接受失败,但我不能接受放弃
如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正。
目录
一、我们为什么要学习string
我们都知道C语言为我们提供了许多有关字符串的库函数,但是这些都不符合C++类的模式,于是C++的官方就为我们通过了string类,方便我们解决有关字符串的问题。
二、怎么样去学习string
下面我将带领大家去认识sring类中的成员函数及其相关知识。
1、string类常见的构造函数
有时候我们要对string类在字符串进行初始化就要用到构造函数
(constructor)函数名称 | 功能说明 |
string() (重点) | 构造空的string类对象,即空字符串 |
string(const char* s) (重点) | 用C-string来构造string类对象 |
string(size_t n, char c) | string类对象中包含n个字符c |
string(const string&s) (重点) | 拷贝构造函数 |
下面我们在代码中理解一下:
//string 的构造函数
void test1()
{
string s1;//构造空的string类对象,即空字符串
string s2("hello world");//用C-string来构造string类对
string s3(s2);//拷贝构造s3
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
}
这里我们可以发现s1对象由于我们没有传参所以,被初始化为空字符。
2、string类对象的容量操作
其实对于string类中存放字符的数据结构,我们可以理解为是一个顺序表,在下面模拟实现的时候将会得以体现。
函数名称 | 功能说明 |
size(重点) | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty (重点) | 检测字符串释放为空串,是返回true,否则返回false |
clear (重点) | 清空有效字符 |
reserve (重点) | 为字符串预留空间 |
resize (重点) | 将有效字符的个数该成n个,多出的空间用字符c填充 |
代码理解:
//string的容量
void test2()
{
string s1("hello world");
cout << s1.size() << endl;//字符有效长度
cout << s1.length() << endl;//字符有效长度
cout << s1.capacity() << endl;//返回空间总大小
cout << s1.empty() << endl;//检测字符串释放为空串,是返回true,否则返回false
string s2;
s2.reserve(20);//为字符串预留空间
cout << s2.capacity() << endl;
s1.resize(20, '*');
cout << s1 << endl;
}
看到上面那么多容量接口,对于有些接口我们不免有一些疑问,比如reserve这个提前开空间有什么用,我们让他们自己进行扩容不可以吗?但是我们利用reserve提高插入数据的效率,避免增容带来的开销,所以说reserve是有必要存在的。
注意:
- . size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一 致,一般情况下基本都是用size()。
- clear()只是将string中有效字符清空,不改变底层空间大小。
- resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字 符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的 元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大 小,如果是将元素个数减少,底层空间总大小不变。
- reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于 string的底层空间总大小时,reserver不会改变容量大小。
这里建议大家,如果遇到不明白的函数,其实我们是可以是相关的C++网站是读文档的,这样有利于培养我们对c++知识学习能力的自学能力。(博主一般在cpluspius网站上进行相关资料的查找)。
我举个例子:当我们不明白resize这的是干嘛的,我们就可以在网站是输入string 类,在找到这个类的容量文档(capacity)找到resize就可以查找了。
3、string类对象的访问及遍历操作
函数名称 | 功能说明 |
operator[] (重 点) | 返回pos位置的字符,const string类对象调用 |
begin+ end | begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭 代器 |
rbegin + rend | begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器 |
范围for | C++11支持更简洁的范围for的新遍历方式 |
下面我将写出三种遍历方式来理解在string类中遍历方式:
void test3()
{
string str1("1234");
//遍历的三种方式
//1 .下标遍历
for (size_t i = 0;i < str1.size();++i)
{
//重载等价于:str1.operator[](i)+1
str1[i]++;
}
cout << str1 << endl;
//2.范围for
for (auto& ch : str1)
{
ch--;
}
cout << str1 << endl;
//反转
size_t begin = 0,end = str1.size() - 1;
while (begin < end)
{
swap(str1[begin++], str1[end--]);
}
//cout << str1 << endl;
//3.迭代器 --通过访问的形式
//string::iterator it1 = str1.begin();
auto it1 = str1.begin();
while (it1 != str1.end())
{
*it1 += 1;
++it1;
}
it1 = str1.begin();
while (it1 != str1.end())
{
cout << *it1 << " ";
++it1;
}
cout << endl;
}
4、string类对象的修改操作
这里我们要借助string的成员函数完成对字符串的修改:
函数名称 | 功能说明 |
push_back | 在字符串后尾插字符c |
append | 在字符串后追加一个字符串 |
operator+= (重点) | 在字符串后追加字符串str |
c_str(重点) | 返回C格式字符串 |
find + npos(重点) | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
rfind | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
我们在对string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般 情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
下面演示一下:
//验证+=
void test4()
{
string s("hello");
cout << s << endl;
s += " world";
cout << s << endl;
}
append和push back都可以达到类似的效果,但是我们通常习惯用+=。
对于c str这个成员函数其实就是返回字符串首地址(也就是数组的首地址)
而find就是在字符串中找到某个字符的在顺序表(认为字符串是用顺序表存放的)中的下标:
5、 string类非成员函数
这些函数虽然不是string的成员函数,但是都是和字符串相关的,我们在OJ刷题的时候可能会用到,这里我们也要认识一下!
函数 | 功能说明 |
operator+ | 尽量少用,因为传值返回,导致深拷贝效率低 |
operator>> (重点) | 输入运算符重载 |
operator<< (重点) | 输出运算符重载 |
getline (重点) | 获取一行字符串 |
relational operators (重点) | 大小比较 |
对于重载+其实他的本质就是调用拷贝构造:
//测试非string的成员函数
void test5()
{
string s1("hello");
string s2(" world");
cout << s1 + s2 << endl;
}
这里的string的非成员函数还重载了流入和流提取 ,不是库中有吗?为什么我们还要重载呢?
这是因为库中的实现的并没有针对性,实现的可能不符合我们要输入或者输出自定义类型,所以我们可能重载出我们符合自己要求的流插入和流提取。(在下面我们会模拟实现<<和>>大家可以细细体会)
对于getline这有什么用了,下面我们来看到一个OJ题
#include <iostream>
using namespace std;
int main()
{
string line;
while(getline(cin,line))
{
size_t pos = line.rfind(' ');
cout<<line.size()-pos-1<<endl;
}
return 0;
}
这里我们可以看到,我们并没有用流提取来获取字符串,这是因为流提取默认提取到空格或者换行就结束了,而对于一个字符串中可能存在空格,这样的话就不能用>>而要用getline或取一行字符。
好了对库中的string我们就先认识到这里,其他的还需要我们学会自己去查文档。
三、对string进行模拟
模拟实现中有许多细节,大家要细细观看噢!
#pragma once
#include<string.h>
#include<assert.h>
#include<istream>
#include<ostream>
using namespace std;
namespace pjb
{
class string
{
typedef char* iterator;//重命名字符指针
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
public:
//这里要注意""这里是个字符串常量,'\0'是一个字符常量,\0是个整形常量
//构造函数
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];//多开一个空间放'\0'
}
//拷贝构造s2(s1)
string(const string& s)
{
_str = new char[s._capacity + 1];//为s2开辟一个s1一样大小的空间
_capacity = s._capacity;
_size = s._size;
strcpy(_str, s._str);
}
//析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
//重载赋值 s1 = s3
string& operator=(const string& s)
{
if (this != &s)//避免this和自己赋值
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp, s._str);
delete[]_str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
//返回字符串的首地址
const char* c_str()const
{
return _str;
}
//返回字符串的长度
size_t size()const
{
return _size;
}
//返回容量
size_t capacity()const
{
return _capacity;
}
//清理
void clear()
{
_size = 0;
_str[0] = '\0';
}
//重载[]
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 reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp,_str);
delete[] _str;
_capacity = n;
}
}
//Resize string
void resize(size_t n, char ch = '\0')
{
if (n > _size)
{
reserve(n);
for (size_t i = _size;i < n;++i)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
else
{
_str[n] = '\0';
_size = n;
}
}
//尾插
void 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 append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);//这里拷贝的时候要注意拷贝的起始位置
_size += len;
}
//这里注意+=可能会有二种传参
string& operator+=(char ch)
{
Push_Back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
//插入数据
string& insert(size_t pos, char ch)
{
assert(pos <= _size);
//判断是否要扩容
if (_size == _capacity)
{
size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newCapacity);
}
//挪动数据
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = ch;
++_size;
return *this;
}
//插入字符串
string& insert(size_t pos, const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
//挪动数据
size_t end = _size + len;
while (end > pos+ len - 1)
{
_str[end] = _str[end - len];
--end;
}
//拷贝从字符串中删除字符
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
//从字符串中删除字符
string& erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
//当len很大或者直接删完
if (len == npos || len >= _size - pos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
//查找
size_t find(char ch, size_t pos = 0)const
{
assert(pos < _size);
while (pos < _size)
{
if (_str[pos] == ch)
return pos;
++pos;
}
return npos;//没找到
}
//查找字符串
size_t find(const char* str, size_t pos = 0)const
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, str);//用strstr暴力查找
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
private:
char* _str;
size_t _size;
size_t _capacity;
const static size_t npos = -1;
};
//重载<<和>>
ostream& operator<<(ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); ++i)
{
out << s[i];
}
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
char buff[128] = { '\0' };
size_t i = 0;
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
if (i == 127)
{
s += buff;
i = 0;
}
buff[i++] = ch;
ch = in.get();
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}