认识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;}//测试sizevoid Test3(){string s1("hello world");size_t num = s1.size();std::cout << num;}
修改容量reserve()
测试原函数效果:
改变后有时会比reserve的n要大有时刚好。
模拟实现:
//修改容量reservevoid reseve(size_t n = 0){if (n > _capacity){char* tmp = new char[n + 1];//开n+1多的1存放\0strcpy(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';}}//修改容量reservevoid reserve(size_t n = 0){if (n > _capacity){char* tmp = new char[n + 1];//开n+1 多的1存放\0strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}//发现两个可以合二为一,但是在扩大capacity中我们需要另外一个函数reservevoid 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()
模拟实现:
//附加appendstring& 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_backvoid 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 = s3string& 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 = s3string& 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,将拷贝和赋值简化