认识string
string是将basic_string<char>重新定义了
basic_string是一个类模板,里面包括了一些列的有关字符的函数
注意:insert/erase/replace能不要就不用,他们都涉及挪动数据,效率不高
size_t注意
面对无符号整形size_t在使用--时,在使用进判断条件时,需要考虑到最后--到0后减不到负的的情况,判断条件还是否成立
解决方法:
将该判断语句的所有无符号整形部分换成或者强转成int类型
构造、析构、拷贝函数
namespace tt//避免与std里的string识别错误,采用命名空间封装 tt->test
{
class string
{
public:
//构造函数
string()
:_str(new char[1])
, _size(0)
, _capacity(0)
//构造时开一个空间存放\0,用于标记
//_size和_capacity=0此时没有字符存入
{
_str[0] = '\0';
}
//析构函数
~string()
{
_size = _capacity = 0;
delete[] _str;
_str = nullptr;//记住在delete后要将指针置空
}
//拷贝构造
//之前的拷贝构造格式是:A(const A& ct),此时我们拷贝的是同一个类型的A,所以用A&去接收
//此时我们拷贝的是字符串,所以里面也用能接收字符串的表示。而char* 类似A&
string(const char* str)
:_size(strlen(str))//算出str长度,可用于改变_size、_capacity
, _capacity(_size )
{
_str = new char[_capacity + 1];//比容量多1用于存放\0,之前构造的\0已经被覆盖
strcpy(_str, str);//会自动拷贝\0
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
void Test1()
{
string s1("hello world");
//直接调用拷贝。"hello world"常量字符串传递过去的是他的地址
}
}
int main()
{
tt::Test1();
return 0;
}
迭代器Iterator
迭代器一共有四种
begin()
返回第一个字符的地址
1.返回第一个字符的地址
2.迭代器是string类中的一个函数
3.iterator是一个类模板的类名,可自动识别不同类型(类似int,一种新定义的对象名称)
测试原函数效果:
#include <iostream>
#include <string>
int main()
{
std::string str("Test string");
std::string::iterator it = str.begin();
//迭代器的类名iterator,对象名it->接受begin返回的地址
std::cout << *it;//begin()返回第一个字符的地址,读取时需要解引用
std::cout << '\n';
return 0;
}
模拟实现:
namespace tt
{
class string
{
public:
string()
:_str(new char[1])
, _size(0)
, _capacity(0)
{
_str[0] = '\0';
}
~string()
{
_size = _capacity = 0;
delete[] _str;
_str = nullptr;
}
string(const char* str)
:_size(strlen(str))
, _capacity(_size )
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
//迭代器
typedef char* iterator;
iterator begin()
{
return _str;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
void Test1()
{
string s1("hello world");
string::iterator it = s1.begin();//it拿到s1第一个字符的地址
std::cout << *it;
}
}
int main()
{
tt::Test1();
return 0;
}
end()
返回最后一个有效字符的下一个字符的地址
测试原函数效果:
注意:using namespace std;==解开全部限制
using std::cout;解开(using)单独的限制
模拟实现:
public:
iterator end()
{
return _str + _size;
}
string类外:
void Test2()
{
string s1("hello world");
string::iterator it = s1.end();
std::cout << *it;
}
结果:
范围for
底层还是迭代器
自动判断结束,自动++,自动赋值
能用下标+方括号[]就能用范围for
auto
麻烦长的话,可以用auto识别,因为begin()返回的就他他的类型,能自动识别
字符串长度size()
计算字符串长度,不包括\0,只计算有效字符(单位:字节)
测试原函数效果:
模拟实现:
public:
//计算有效字符字节数
size_t size()const
{
return _size;
}
//测试size
void Test3()
{
string s1("hello world");
size_t num = s1.size();
std::cout << num;
}
修改容量reserve()
测试原函数效果:
改变后有时会比reserve的n要大有时刚好。
模拟实现:
//修改容量reserve
void reseve(size_t n = 0)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];//开n+1多的1存放\0
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
修改字符串长度resize()
1.resize多加的无法打印出来,加的都是\0,可以通过监视窗口查看
2.加实际值则会显示出来
3.如果比容量大会自动扩容
4.比容量少相当于保留前n个字符(删除的意义)
测试原函数效果:
模拟实现:
//修改字符串长度
//复杂
void resize(size_t n)
{
if (n <= _size)
{
_size = n;
}
else if (n > _size && n < _capacity)
{
while (_size<n)
{
_str[_size] = '\0';
_size++;
}
_str[_size] = '\0';
}
else if (n >= _capacity)
{
_capacity = n;
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
while (_size < n)
{
_str[_size] = '\0';
_size++;
}
_str[_size] = '\0';
}
}
//修改容量reserve
void reserve(size_t n = 0)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];//开n+1 多的1存放\0
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
//发现两个可以合二为一,但是在扩大capacity中我们需要另外一个函数reserve
void resize(size_t n, char c = '\0')//函数重载
{
if (n < _size)
{
_str[n] = '\0';
_size = n;
}
else//>分两种,在capacity里和超出capacity
{
reserve(n);//不管大小,直接进行重新拷贝
while (_size<n)
{
_str[_size] = c;//存第n个数下标为n-1就可以存
++_size;//++后存的是第n+1个数
}
_str[_size] = '\0';//n+1存\0,下标为n
}
}
清楚clear()
容量不变,size改变
测试原函数效果:
模拟实现:
//清空内容
void clear()
{
_str[0] = '\0';
_size = 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];
}
附加append()
模拟实现:
//附加append
string& append(const char* s)
{
size_t len = strlen(s);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, s);//从末尾附加新字符串
_size += len;
return *this;
}
重载+=
测试原函数效果:
模拟实现:
//重载+=
string& operator+=(const char* s)
{
append(s);
return *this;
}
//尾插push_back
void push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
//新开空间要注意为0的情况
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
string& operator+=(const char c)
{
push_back(c);
return *this;
}
返回字符串地址(指针)c_str
模拟实现:
//返回字符串地址(指针)
const char* c_str() const
{
return _str;
}
npos
查找find()
1.返回的是查找到的第一个字符或字符串出现位置的下标
2.pos是下标
3.从pos的位置向后查找
模拟实现:
size_t find(char ch, size_t pos = 0)
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
size_t find(const char* sub, size_t pos = 0)
{
const char* p = strstr(_str + pos, sub);
if (p)
{
return p - _str;
}
else
{
return npos;
}
}
使用:
cin注意
scanf和cin面对空格和\0都是间隔,读取时一个变量就无法读取间隔后面的内容,所以使用getline
识别到!才结束
>>输入重载
//输入重载
istream& operator>>(istream& in, string& s)
{
s.clear();//将原s中的字符串清空
char ch;
//in >> ch;
ch = in.get();//先从这里拿一个字符,用于下面while判断
s.reserve(128);
while (ch != ' ' && ch != '\n')//修改结束条件
{
s += ch;
//in >> ch;
ch = in.get();//一个字符一个字符拿
}
return in;
}
巧用数组当缓冲
理解想法就行
如果直接用reserve可能会造成输入的数少但是开的空间大,而用数组的话,在拿取时更加符合输入内容的空间大小,并且在出了作用域就会销毁
istream& operator>>(istream& in, string& s)
{
s.clear();
//s.reserve(128);
char buff[129];//开一个数组,存储128个字符,第129位存储\0,当做缓存
size_t i = 0;
char ch;
ch = in.get();
while (ch != ' ' && ch != '\n')//识别到空格或者换行出循环后,判断if
{
buff[i++] = ch;
//输入的字符大于128个时,将缓存部分全部放入字符串中,
//继续读取之后的字符并覆盖之前buff的字符
if (i == 128)//可以存储129为对应下标为128,同时避免输入的超出数组
{
buff[i] = '\0';
s += buff;
i = 0;//标志
}
//s += ch;
ch = in.get();
}
if (i != 0)//说明buff数组没满,保险
{
buff[i] = '\0';
s += buff;
}
return in;
}
<<输出重载
ostream& operator<<(ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); i++)
{
out << s[i];
}
return out;//为了连续<<时可用
}
赋值的现代写法(swap)
swap数组交换优点核心:
不仅交换了,还将自己要释放的交换给了别人
老版本:打工人自己搬砖
//传统写法
s2(s1)
string(const string& s)
{
_str = new char[s._capacity+1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
// s2 = 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;
}
现代版本:
//拷贝构造1
string(const char* str)
:_size(strlen(str))//算出str长度,可用于改变_size、_capacity
, _capacity(_size )
{
_str = new char[_capacity + 1];//比容量多1用于存放\0,之前构造的\0已经被覆盖
strcpy(_str, str);//会自动拷贝\0
}
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
// s2(s1)拷贝构造2
//两个拷贝构造构成重载
string(const string& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
string tmp(s._str);
swap(tmp);
}
// s2 = s3
string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s);
//this->swap(tmp);
swap(tmp);
}
return *this;
}
//更极致的现代写法
string& operator=(string tmp)
{
swap(tmp);
return *this;
}
void Test7()
{
string s1("hello world");
string s2;
s2 = s1;
cout << s1 << endl;
cout << s2 << endl;
}
这一部分转换的swap在s2转移给了tmp,而tmp又是在局部的变量,出了作用域会销毁(析构函数),顺便就解决了
并且通过简单地一个局部变量tmp,再利用swap,将拷贝和赋值简化