-----“人生的上半场打不好没关系,还有下半场”
这篇博客主要针对C++ STL中串(string)的功能使用,以及其中重要功能的模拟实现。
(1)什么是string?
C语言中,string就是字符串,是以'\0'结尾的一些字符的集合。
但字符串本身和库函数是分离,需要用户去底层管理字符串,对于"马大哈"的用户容易越界访问。
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) | 拷贝构造 |
②容量操作:
函数名称 | 功能说明 |
size | 返回字符串有效长度 |
length | 返回字符串有效长度 |
capacity | 返回空间总大小 |
emepty | 检测字符串释放为空串,是返回true,否则返回false |
clear | 清空有效字符 |
reserve | 为字符串预留空间 |
resize | 将有效字符的个数该成n个,多出的空间用字符c填充 |
rsize 和 reserve:
resize:
reserve:
string s1("hello");
s1.reserve(20);
cout << s1 << endl;
s1.resize(10, 'c');
cout << s1 << endl;
结论:单从结果上来看resize 与 reserve的差别在于,扩容后,是否增加新的字符(初始化)。
③字符串遍历:
1.用数组的方式[].
在string库中,重载来[]来访问的operator:
string s1("hello bit");
for (int i = 0 ; i < s1.size() ; i++)
{
cout << s1[i] << " ";
}
cout << endl;
2.迭代器:
string s1("hello vscode");
//指名 迭代器属于哪个类里面的
string::iterator it = s1.begin();
while (it != s1.end())
{
//同样也可以修改
*it += 1;
cout << *it <<" ";
it++;
}
cout << endl;
3.范围for:
之后模拟实现会说说,范围for的原理。
④string类对象的修改:
刚刚呢,我们可以通过下标,对具体某个值的改变。
函数名称 | 功能说明 |
push_back | 在字符串后尾插字符c |
append | 在字符串后追加一个字符串 |
operator+= | 在字符串后追加字符串str |
c_str | 返回C格式字符串 |
find+npos | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
rfind | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
1.增加(尾增)
可以看出,使用operator可读性强,且就是append和push_back 的复用。
2.查找:
string str("file.cpp");
//默认从(起始)地址处往后找 找到了就返回第一个下标位置
size_t pos1 = str.find("cpp",3);
cout << pos1 << endl;
size_t pos2 = str.find("h", 0);
cout << pos2 << endl;
// npos是string里面的一个静态成员变量
static const size_t npos = -1;
如果找不到,就直接返回npos,也就是-1:
⑤string类非成员函数:
函数 | 功能说明 |
operator+ | 尽量少用,因为传值返回,导致深拷贝效率低 |
operator>> | 输入运算符重载 |
operator<< | 输出运算符重载 |
getline | 获取一段字符串 |
relational operators | 大小比较 |
⑥其他类:
函数 | 功能 |
insert | 在pos位置处插入字符 |
erase | 在pos~n位置删除字符 |
insert:
在pos位置之前插入。
erase:
当erase没有给任何值的时候,也就是全部删除。
(2)string的模拟实现:
1.构造函数与析构;
//不好的写法string(const char* str="\0") 这里会有两个'\0'
string(const char* str="")
{
_size = strlen(str);
_capacity = _size;
//多开一个空间放"\0"
_str = new char[_capacity+ 1];
//并把值拷贝过来
strcpy(_str, str);
}
//析构
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
字符串和空字符串:
2.拷贝构造和赋值重载;
//拷贝构造
//s2(s1)
string(const string& s)
:_str(new char[strlen(s._str) + 1]),
_size(s._size),
_capacity(s._capacity) //被拷贝构造的 类 直接给定原数
{
//再加上内容拷贝
strcpy(_str, s._str);
}
//赋值运算符重载
string& operator=(const string& s)
{
//把自己的内存释放掉
delete[] _str;
//开和它一样大的空间
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
return *this;
}
拷贝构造:
赋值运算重载:
上面两种是传统写法:
接下来,写一个现代写法:其思想就是交换。
现代拷贝构造和赋值重载:
代码简单。其跟本思想,就是去构造一个类,并把要做的事情让要构造的类,先和它做。
我们看到两份代码,在swap的时候,有相同的步骤,所以可以进行合并如下:
//此时需要注意 此刻的swap 和 string 的库函数swap同名,因为原则是就近 但需要使用全局函数(库函数) 所以需要加::让编译器去全局找函数
void swap(string& tmp)
{
::swap(_str, tmp._str);
::swap(_size, tmp._size);
::swap(_capacity, tmp._capacity);
}
string(const string& s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
//创建一个tmp 并由s 构造
string tmp(s._str);
//this->swap(tmp)
swap(tmp);
/* swap(_str,tmp._str);
swap(_size, tmp._size);
swap(_capacity, tmp._capacity);*/
}
//s1=s3
string& operator=(const string& s)
{
string tmp(s._str);
swap(tmp);
/*swap(_str, tmp._str);
swap(_size, tmp._size);
swap(_capacity, tmp._capacity);*/
return *this;
}
3.string遍历:
operator [] 1.
//返回pos位置的值
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
可更改数值:
operator [] 2.
//修饰返回值 //修饰this 因为是const string
const char& operator[](size_t pos)const
{
assert(pos < _size);
return _str[pos];
}
无法改变值
迭代器:
string是类似于指针。
//首先重定义
typedef char* iterator
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
同样,也可以设置通过迭代器不可更改。
typedef const char* const_itertor;
//返回值 和 this 参数 都用const修饰
const_itertor begin()const
{
return _str;
}
const_itertor end()const
{
return _str+_size;
}
注: 这里迭代器的形式和指针一样,仅仅是在string类这里是这样
范围for:
范围for虽然很神奇,但其中的原理很简单。其依托的就是迭代器。
当我们把迭代器的一侧屏蔽>_
for范围,在编译的过程中,会被编译器自动看成迭代器!!
4.增加与扩容:
扩容:
resize:扩容并初始化
//扩容
//rsize ---->不止扩容 还初始化
void resize(size_t n,char val='\0')
{
//1.重设置 n<size
if (n < _size)
{
_size = n;
_str[_size] = '\0';
}
else
{
//2.不止补满capacity
if (n > _capacity)
{
}
//3.补在capacity以内
for (int i = _size;i < n;++i)
{
//初始化
_str[i] = val;
}
_str[n] = '\0';
_size = n;
}
}
reserve:仅扩容
void reserve(size_t n)
{
//为什么要判断? 解耦合
if (n > _capacity)
{
char* tmp = new char[n + 1];
//不把'\0'拷贝过去
strncpy(tmp, _str, _size);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
push_back:
void push_back(char ch)
{
//增容
if (_size == _capacity)
{
//开2倍
reserve(_capacity==0?4:_capacity*2);
}
_str[_size] = ch;
//不要忘了
_str[_size + 1] = '\0';
_size++;
}
append:
void append(const char* str)
{
//增加字符串 可能>capacity 也可能<capacity
size_t len = _size + strlen(str);
if (len > _capacity)
{
reserve(len);
}
strcpy(_str + _size, str);
_size = len;
}
operator+=:
//复用
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* ch)
{
append(ch);
return *this;
}
5.插入(insert)、删除(erase)与查找(find):
insert(单个字符):
//在pos位置 之前插入
string& insert(size_t pos, char ch)
{
//插入_size 就是尾插了
assert(pos <= _size);
if (_capacity == _size)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
//用下标
size_t end = _size;
while (end >=pos)
{
_str[end + 1] = _str[end];
--end;
}
_str[pos] = ch;
_size++;
return *this;
}
可以发现当要头插,因为end为无符号整数,那么当end=-1时,会是一个很大的数导致程序崩死。
改良:
string& insert(size_t pos, char ch)
{
//插入_size 就是尾插了
assert(pos <= _size);
if (_capacity == _size)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
//用下标
/*size_t end = _size;
while (end >=pos)
{
_str[end + 1] = _str[end];
--end;
}*/
//用指针 可以避免 size_t的较大值
char* end = _str + _size;
while (end >= _str + pos)
{
*(end + 1) = *end;
--end;
}
_str[pos] = ch;
_size++;
return *this;
}
insert(字符串):
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len=strlen(str);
if (len+_size > _capacity)
{
reserve(len + _size);
}
//挪动数据
char* end = _str + _size;
while (end >= _str + pos)
{
*(end + len) = *end;
--end;
}
//拷贝多少字符就+多少
strncpy(_str+pos,str, len);
_size += len;
return *this;
}
erase:
static const size_t npos;
const size_t string::npos = -1;
//注: npos是全局变量
string& erase(size_t pos = 0, size_t len=npos)
{
assert(pos <= _size);
//1.剩余字符小于 要删长度
//2.剩余字符大于 要删长度
size_t leftlen = _size - pos; //剩余字符
if (len > leftlen)
{
_str[pos] = '\0';
_size = pos;
}
else
{
//把leftlen往前拷贝 //越过len+pos(要删除的字符)
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
find:
size_t find(char ch, size_t pos = 0)
{
assert(pos < _size);
int i = pos;
for (;i < _size;i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
6.输入输出:
//输出
ostream& operator<<(ostream& out, const string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
//输入
istream& operator>>(istream& in,string& s)
{
//每次输入前先清空原有的地方
s.clear();
char ch;
//获取字符
ch = in.get();
while (ch != ' ' && ch != '\n')
{
//利用增容机制
s += ch;
ch = in.get();
}
return in;
}
getline:
istream& getline(istream& in, string& s)
{
//每次输入前先清空原有的地方
s.clear();
char ch;
//获取字符
ch = in.get();
while ( ch != '\n')
{
//利用增容机制
s += ch;
ch = in.get();
}
return in;
}
最后也就到这里 了,果然挺多了~
祝你好运~