一.String容器是一个类模板
但是在定义string类对象的时候却不用显示实例化,是因为:
二.String类对象的实例化
在使用string对象的时候首先就要意识到自己是在操作一段连续的存储空间,这段存储空间有标识数据量的,有标识存储量,有标识存储空间首元素的地址的。
const对象和非const对象的在构造函数和析构函数上一样,因为系统认为初始化列表是对象实例化的地方。
#pragma once
#include <iostream>
using namespace std;
namespace black
{
class Mystring
{
public:
//成员方法
//默认成员函数
//构造函数
Mystring(const char* str = "")
:_size(strlen(str))
,_capacity(_size)
,_str(new char[_capacity + 1])
{
memcpy(_str, str, _size + 1);
_str[_size] = '/0'; //也可以是*(_str + _size)
}
//析构函数
~Mystring()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
//成员变量
private:
size_t _size; //字符串数据量
size_t _capacity; //字符串容量
char* _str; //字符串首元素地址
};
}
在 memcpy(_str, str, _size + 1); 中为什么是_size + 1,因为首先_size(strlen(str))strlen是计算字符串除/0以外的所有的字符个数,所以实际的数据数量比计算得到的大1,所以在拷贝的时候需要加1.
三.String类对象获取数据并且遍历数据
const对象无法使用push_back append += 来尾插数据
1.获取数据
a.push_back
void push_back(const char c_character)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : (_capacity * 2));
}
*(_str + _size) = c_character;
_size++;
*(_str + _size) = '/0';
}
reserve会改变string类对象的连续存储空间,大于capacity会增加容量,小于等于一般是不进行操作或者不进行缩容,因为这个缩容的过程无非也是从系统中重新申请一段空间然后拷贝原空间的数据,然后释放原空间,在将新空间的地址给string对象保存这个过程非常的有损效率,但是在vs2019中如果在进行缩容操作前先进行clear操作后是会缩容的,不同的编译器有不同的操作。
//扩容
void reserve(size_t st_size)
{
if (st_size > _capacity)
{
char* str = new char[st_size + 1];
memcpy(str, _str, _size + 1);
delete[] _str;
_str = str;
_capacity = st_size;
}
}
resize和reserve的功能类似,但是resize在size小于capacity时会删除数据,是否会缩容不确定,在size大于capacity时会对新申请的空间进行初始化,所以string类对象的size也会被改变。
void resize(size_t st_size, char c_tmp = '/0')
{
if (st_size > _capacity )
{
char* str = new char[st_size + 1];
memcpy(str, _str, _size + 1);
delete[] _str;
_str = str;
_capacity = st_size;
int i_i = 0;
for (i_i = _size; i_i < _capacity; i_i++)
{
if (c_tmp != '/0')
{
*(_str + i_i) = c_tmp;
_size++;
}
else
{
*(_str + i_i) = c_tmp;
}
}
}
else if (st_size < _capacity)
{
_str[st_size] = '/0';
_size = st_size;
}
}
b.append
append是尾插一段数据
void append(const char* c_str)
{
int i_length = strlen(c_str);
if ( _size + i_length > _capacity)
{
reserve(_size + i_length);
}
memcpy(_str + _size, c_str, i_length + 1);
_size += i_length;
}
c.+=是对push_back和append复用
//实现+=
//单个字符
string& operator+=(const char c_charactor)
{
push_back(c_charactor);
return *this;
}
//一个字符串
string& operator+=(const char* c_str)
{
append(c_str);
return *this;
}
//一个对象
string& operator+=(const string& cls_str)
{
append(cls_str._str);
return *this;
}
遍历
public:
typedef char* iterator;
typedef const char* const_iterator;
char& operator[](size_t index)
{
if (index >= 0 && index < _size)
{
return *(_str + index);
}
else
{
cout << "超出数据范围!" << endl;
}
}
const char& operator[](size_t index)const
{
if (index >= 0 && index < _size)
{
return *(_str + index);
}
else
{
cout << "超出数据范围!" << endl;
}
}
//迭代器的实现
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
black::string::iterator it = str_two.begin();
while (it != str_two.end())
{
cout << *it << " ";
it++;
}
cout << endl;
black::string::const_iterator itc = str_three.begin();
while (itc != str_three.end())
{
cout << *itc << " ";
itc++;
}
cout << endl;
四.insert和erase
insert:在指定位置后插一定数量的单个数据或一段数据。
erase:在指定位置后删一定数量的数据。
//在指定位置插入一定数量的单个数据或者一整段数据
//单个数据
string& insert(size_t index, size_t nums, const char cs_character)
{
//判断数据是否正确
assert(index <= _size);
//判断字符串空间是否足够
int i_length = _size + nums;
if (i_length >= _capacity)
{
reserve(i_length);
}
//挪动数据位置
int i_i = 0;
//通过强制类型转换来解决由于整形提升转换导致的无线循环的问题
for (i_i = _size; i_i >= (int)(index); i_i--)
{
*(_str + nums + i_i) = *(_str + i_i);
}
//放入数据
int i_quantity = _size;
for (i_i = 0; i_i < nums; i_i++)
{
*(_str + i_i + index) = cs_character;
_size++;
}
return *this;
}
因为pos是size_t类型的数据所以,在进行比较时会发生整形提升,将int类型隐式转变为size_t类型的数据,但是此时又因为有等于的存在,所有当pos等于0时,end不可能变为比pos小的数据所以此时会发生无线循环。
//一段数据
string& insert(size_t index, const char* c_str)
{
assert(index <= _size);
int i_length = strlen(c_str);
if (_size + i_length >= _capacity)
{
reserve(_size + i_length);
}
//挪动数据位置
int i_i = 0;
//通过强制类型转换来解决由于整形提升转换导致的无线循环的问题
for (i_i = _size; i_i >= (int)(index); i_i--)
{
*(_str + i_length + i_i) = *(_str + i_i);
}
//放入数据
memcpy(_str + index, c_str, i_length);
return *this;
}
string& erase(size_t index, size_t len = npos)
{
assert(index <= _size);
if (len == npos || (len + index) >= _size)
{
*(_str + index) = '\0';
_size = index;
}
else
{
int i_i = 0;
int i_end = index + len;
while (i_end <= _size)
{
_str[index++] = _str[i_end++];
}
_size -= len;
}
return *this;
}
五.find
从指定位置开始查找符合的字符串或字符,如果找到了返回string对象中符合条件的字符串的首元素的索引,没有找到返回npos。
//实现find
//查找单个字符
size_t find(const char cs_charactor, size_t st_index = 0)
{
//判断数据是否正确
assert(st_index <= _size);
//开始查找
int i_i = st_index;
while (i_i < _size)
{
if (*(_str + i_i) == cs_charactor)
{
return i_i;
}
i_i++;
}
return npos;
}
//查找目标字符串
size_t find(const char* cc_str, size_t st_index = 0)
{
//判断数据是否正确
assert(st_index <= _size);
//开始查找
const char* cc_str_tmp = strstr(_str + st_index, cc_str);
//判断是否有目标字符串
if (cc_str_tmp)
{
return cc_str_tmp - _str;
}
return npos;
}
strstr函数:
头文件:<string.h>
函数声明:char *strstr(char *str1, const char *str2); //返回值为字符型指针
str1: 被查找目标
str2: 要查找对象
strstr() 函数搜索一个字符串在另一个字符串中的第一次出现。
找到所搜索的字符串,则该函数返回第一次匹配的字符串的地址;
如果未找到所搜索的字符串,则返回NULL
六.substr
从指定位置开始截取len长度的字符串
//实现substr
const char* substr(size_t st_index = 0, size_t st_len = npos)
{
//判断数据是否正确
assert(st_index <= _size);
//确定实际的截取长度
if (st_len == npos || st_len > _size)
{
st_len = _size - st_index;
}
//创建存储截取字符串的存储空间
char* cc_new_str = new char[st_len + 1];
//开始复制数据
int i_i = st_index;
int i_j = 0;
while (i_j <= st_len)
{
*(cc_new_str + i_j) = *(_str + i_i);
i_i++;
i_j++;
}
*(cc_new_str + st_len) = '\0';
return cc_new_str;
}
七.string对象之间的关系运算符
//实现string对象的比较
bool operator<(const string& str)const
{
//memecmp是位比较函数返回零说明两段空间中的数据相同 >0 <0
int ret = memcmp(_str, str._str, (_size < str._size ? _size : str._size));
return ret == 0 ? _size < str._size : ret < 0;
}
bool operator== (const string & str)const
{
return (_size == str._size)
&&
!memcmp(_str, str._str, _size);
}
bool operator <= (const string& str)const
{
return (*this == str) || (*this < str);
}
bool operator>(const string& str)const
{
return !(*this <= str);
}
bool operator>=(const string& str)const
{
return !(*this < str);
}
memcmp:比较两个内存块
将 ptr1 指向的内存块的第一个 num 字节与 ptr2 指向的第一个 num 字节进行比较,如果它们都匹配,则返回零,或者如果它们不匹配,则返回与零不同的值,表示哪个值更大。请注意,与 strcmp 不同,该函数在找到 null 字符后不会停止比较。
八.输入输出流运算符的重载
系统中ostream和istream对象有防拷贝的检测,如果这里把这两类对象的引用去掉会报错。
string对象的输出和不同的字符串的不同是,string对象是输出到等于size,而普通的字符串是直接输出遇到\0字符结束。
//实现clear数据
void clear()
{
_str[0] = '\0';
_size = 0;
}
ostream& operator<<(ostream& cout, const string& str_tmp)
{
//cout << str_tmp.c_str();
for (auto e : str_tmp)
{
cout << e;
}
return cout;
}
istream& operator>>(istream& cin, string& str_tmp)
{
//清空原空间中的数据
str_tmp.clear();
//定义一个数组用来预存放获取到的数据
char buff[128];
//定义一个变量来获取数据
int ch = 0;
//使cin从第一个非空白字符开始读取
ch = cin.get();
while (ch == ' ' || ch == '\n')
{
ch = cin.get();
}
//将数据存放到buff数组中,如果数据量超过127那么要将数据放到string对象中
int i_i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i_i++] = ch;
if (i_i == 127)
{
buff[i_i] = '\0';
str_tmp += buff;
i_i = 0;
}
ch = cin.get();
}
if (i_i != 0)
{
buff[i_i] = '\0';
int size = strlen(buff);
str_tmp += buff;
}
*(str_tmp._str + str_tmp._size) = '\0';
return cin;
}
九.拷贝构造函数和复制重载
//拷贝构造函数
string(const string& str_tmp)
{
_str = new char[str_tmp.capacity() + 1];
memcpy(_str, str_tmp.c_str(), str_tmp.size() + 1);
_size = str_tmp.size();
_capacity = str_tmp.capacity();
}
//赋值重载
string& operator=(string str_tmp)
{
Swap(*this, str_tmp);
return *this;
}
void Swap(string& str1, string& str2)
{
swap(str1._str, str2._str);
swap(str1._size, str2._size);
swap(str1._capacity, str2._capacity);
}