👊👊你间歇性的努力和蒙混过日子,都是对之前努力的清零!
目录
1.4.2 operator+= (在字符串后追加字符串str)
1.4.4 find+npos (从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置)
1.4.5 substr(在str中从pos位置开始,截取n个字符,然后将其返回)
1.5.2 relational operators (比较函数)
2.4.1 operator[](获取字符数组指定位置字符)
2.5.4 append(从某个字符串截取部分尾插到字符数组中)
2.5.5 insert(指定位置插入字符/字符串 💡需要挪动数据 注重临界!!)
2.6.3 getline(可以获取输入的空格,也申明为友元)
一、string类重要成员函数
1.1 构造函数
(constructor)函数名称 | 功能说明 |
string() (重点) | 构造空的string类对象,即空字符串 |
string(const char* s) (重点) | 用C-string来构造string类对象 |
string(size_t n, char c) | string类对象中包含n个字符c |
string(const string&s) (重点) | 拷贝构造函数 |
1.2 类对象容量操作
函数名称 | 功能说明 |
size(重点) | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty (重点) | 检测字符串释放为空串,是返回true,否则返回false |
clear (重点) | 清空有效字符 |
reserve (重点) | 为字符串预留空间** |
resize (重点) | 将有效字符的个数该成n个,多出的空间用字符c填充 |
💡resize
如果 n 小于当前字符串长度,则当前值将缩短为其第一个 n 个字符,删除第n个字符以外的字符。
如果 n 大于当前字符串长度,则通过在末尾插入所需数量的字符以达到n大小来扩展当前内容。如果指定了 c,则新元素初始化为c 的副本,否则,它们是值初始化字符(空字符)。💡💡这里提一下resize函数间接影响capacity三种情况(对n进行分类讨论):
1.n>原capacity 扩容
2.n=原capacity 不变
3.n<原capacity 不变
1.3 string类对象的访问及遍历操作
函数名称 | 功能说明 |
operator[] (重 点) | 返回pos位置的字符,const string类对象调用 |
begin+ end | begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭 代器 |
rbegin + rend | begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭 代器 |
范围for | C++11支持更简洁的范围for的新遍历方式(底层靠迭代器实现) |
int main()
{
string s("hello world!");
//1.运算符重载[]实现
for (int i = 0;i < s.size();i++)
{
cout << s[i];
}
cout << endl;
//2.迭代器
string::iterator it = s.begin();//获取头字符
while (it != s.end())
{
cout << *it;
it++;
}
cout << endl;
//3.范围for(底层依旧是迭代器)
for (auto ch : s)
{
cout << ch;
}
cout << endl;
//4.反向迭代器(从后往前)
string::reverse_iterator rit = s.rbegin();//取尾
while (rit != s.rend())
{
cout << *rit;
rit++;
}
cout << endl;
return 0;
}
1.4 string类对象的修改操作
函数名称 | 功能说明 |
push_back | 在字符串后尾插字符c |
append | 在字符串后追加一个字符串 |
operator+= (重点) | 在字符串后追加字符串str |
c_str(重点) | 返回C格式字符串 |
find + npos(重点) | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
rfind | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
1.4.1 c_str (返回C格式字符串)
const char* c_str() const
1.4.2 operator+= (在字符串后追加字符串str)
string (1) string& operator+= (const string& str);c-string (2) string& operator+= (const char* s);character (3) string& operator+= (char c);
1.4.3 npos
(size_t npos=-1 表示无符号整型) 是类的静态私有成员,是整形能够表示的最大值
1.4.4 find+npos (从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置)
返回值:第一个匹配项的第一个字符的位置,如果未找到匹配项,则该函数返回string::npos
string (1) size_t find (const string& str, size_t pos = 0) const;c-string (2) size_t find (const char* s, size_t pos = 0) const;buffer (3) size_t find (const char* s, size_t pos, size_t n) const;character (4) size_t find (char c, size_t pos = 0) const;
1.4.5 substr(在str中从pos位置开始,截取n个字符,然后将其返回)
申明:string substr (size_t pos = 0, size_t len = npos) const ;
1.5 string类非成员函数
函数 | 功能说明 |
operator+ | 尽量少用,因为传值返回,导致深拷贝效率低 |
operator>> (重点) | 输入运算符重载 |
operator<< (重点) | 输出运算符重载 |
getline (重点) | 获取一行字符串 |
relational operators (重点) | 大小比较 |
1.5.1 operator<< 和 getline
💡💡 cin读取不了空格,空格表示分隔
比如你输入hello world! 输出只有hello
那么如何获取空格呢? getline()函数可以
(1) istream& getline (istream& is, string& str, char delim);(2) istream& getline (istream& is, string& str);
//要包头文件 #include<string> int main () { std::string name; std::cout << "Please, enter your full name: "; std::getline (std::cin,name); std::cout << "Hello, " << name << "!\n"; return 0; }
1.5.2 relational operators (比较函数)
(1)
bool operator== (const string& lhs, const string& rhs); bool operator== (const char* lhs, const string& rhs); bool operator== (const string& lhs, const char* rhs);(2) bool operator!= (const string& lhs, const string& rhs); bool operator!= (const char* lhs, const string& rhs); bool operator!= (const string& lhs, const char* rhs);(3) bool operator< (const string& lhs, const string& rhs); bool operator< (const char* lhs, const string& rhs); bool operator< (const string& lhs, const char* rhs);(4) bool operator<= (const string& lhs, const string& rhs); bool operator<= (const char* lhs, const string& rhs); bool operator<= (const string& lhs, const char* rhs);(5) bool operator> (const string& lhs, const string& rhs); bool operator> (const char* lhs, const string& rhs); bool operator> (const string& lhs, const char* rhs);(6) bool operator>= (const string& lhs, const string& rhs); bool operator>= (const char* lhs, const string& rhs); bool operator>= (const string& lhs, const char* rhs);
二、string类自我模拟实现主要成员函数
2.1 string类的基本框架
💡为了与std::string不冲突,我们自己开了一个命名空间,在命名空间内调用自己写的string!
//基本框架
namespace wyz
{
class string
{
public:
private:
char* _str;//指向char 数组的指针
size_t _capacity;//数组容量
size_t _size;//有效字符个数
const static size_t npos = -1;
};
}
2.2 构造函数和析构函数
string(const char* str)
{
_capacity = strlen(str);
//!注意要多开一个空间给'\0'
_str = new char[_capacity+1];
_size = strlen(str);
strcpy(_str, str);
}
//拷贝构造(传统写法)
string(const string& str)
{
_capacity = str._capacity;
_size = str._size;
//注意深浅拷贝区别!
_str = new char[str._capacity+1];
strcpy(_str, str._str);
}
//不传参情况
string()
{
_str = new char[1];
_str[0] = '\0';
_capacity = 0;
_size = 0;
}
//析构函数
~string()
{
delete[]_str;
_str = nullptr;
_capacity = _size = 0;
}
★2.3 “大佬”写的拷贝构造与赋值构造函数(深拷贝!)
先用一个不巧当的比喻来概述这段大佬写的拷贝构造与赋值构造函数!
主角是一个可怜打工仔,临时变量tmp!
拷贝构造:
给出一个例子:string s1="hello world!" string s2(s1);
这里s2统一说成*this!
1.首先我们用先前写的传C类型指针构造tmp,这样tmp指向空间内容和s1一样,!但是tmp_str指向的空间不是和s1一样(深拷贝)!_size,_capacity和s1一样!
2.初始化tmp好了以后,眼光放到*this这边!这里先告诉读者我们必须初始化s2,之后会讲为什么一定要初始化!我们要将s1拷贝给s2,临时变量已经创建好,现在只需要交换,交换的内容很简单就是内置成员变量,指针+容量+有效字符个数 给*this,这样tmp拿到了初始化的s2的内置成员值,完成拷贝。
3.出了函数。注意!这里tmp是临时类,出了作用域要自动调用它的析构函数!现在假如*this没有初始化,也就说*this的_str是野指针,析构函数delete不能释放野指针指向的空间,会程序崩溃!这就是为什么一定要初始化s2即*this!
赋值重载:s2=s1
同样需要创建临时变量,我们写过拷贝构造以后可以直接不引用传参,而是拷贝传参给tmp!这里因为s2已经初始化,所以没有上面的初始化,直接将两者交换!同样出了函数,tmp指向s2原指向的空间,出了函数会自动调用析构函数!
所以最终代码简简单单地就是几行!
//传统拷贝构造
/*string(const string& str)
{
_capacity = str._capacity;
_size = str._size;
_str = new char[str._capacity];
strcpy(_str, str._str);
}*/
//交换函数
void swap(string& tmp)
{
std::swap(_str, tmp._str);
std::swap(_size, tmp._size);
std::swap(_capacity, tmp._capacity);
}
//现代拷贝构造
string(const string& str)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
string tmp(str.c_str());//注意这里用构造函数!
swap(tmp);
}
//赋值重载
string& operator=(string tmp)//注意一个细节变化!这里传参的时候用直接用拷贝构造tmp
{
swap(tmp);
return *this;
//交换完以后,原来*this指向的字符串的空间交换给了tmp,tmp是临时变量,出了这个函数就会自动调用析构函数,将原来*this指向的空间释放掉!真是纯纯打工仔冤种!
}
2.3 类对象操作函数
2.3.1 size&&length(返回有效字符个数)
size_t size()
{
return _size;
}
2.3.2 capacity(返回开辟空间大小)
//返回空间大小
size_t capacity()
{
return _capacity;
}
2.3.3 empty(判断类字符数组有效字符是否为0)
//判断是否为空
bool empyt()
{
return _size == 0;
}
2.3.4 clear(清理字符数组)
//清空字符数组
void clear()
{
_str[0] = '\0';
_size =0;
}
★★2.3.5 reserve (提前设定空间容量大小)
//开辟空间
void reserve(size_t capacity)
{
//先保存原数组内容
char* tmp = _str;
//创建新数组
_str = new char[capacity + 1];
//拷贝
strcpy(_str, tmp);
_capacity = capacity;
delete[]tmp;
}
★★2.3.6 resize (修改有效字符个数)
//修改size
void resize(size_t size)
{
if (size > _capacity)
{
//必须先扩容
(*this).reserve(size);
//没传字符参数,将扩容空间放入空字符,这里参数数字很重要!很容易这里出错!!!
memset(_str + size, ' ', size - _size);
}
//最终都要让size位置变为'\0'
_str[size] = '\0';
_size = size;
}
void resize(size_t size, char ch)
{
if (size > _capacity)
{
(*this).reserve(size);
memset(_str + _size, ch, size - _size);
}
_str[size] = '\0';
_size = size;
}
2.4 string类对象的访问及遍历操作函数
2.4.1 operator[](获取字符数组指定位置字符)
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
支持const string 类型访问
const char& operator[](size_t pos)const
{
assert(pos < _size);
return _str[pos];
}
2.4.2 begin/end(返回头/尾指针)
typedef char* iterator;
//返回字符串头
iterator begin()
{
return _str;
}
返回字符串尾'\0'
iterator end()
{
return _str + _size;
}
2.5 string类对象的修改操作函数
2.5.1 c_str(获取字符数组指针)
//返回字符数组头部指针
const char* c_str()const
{
return _str;
}
2.5.2 push_back(尾插)
//尾插字符
void Push_back(char ch)
{
//如果空间已满,分两路讨论扩容 1.原来是空串 初始不能*=2,要先赋值给4个字符的空间
//2.先前不是空串,那么扩2倍(自己选择)
size_t newcapacity = _capacity == _size == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
//尾插,将之前'\0'位置放字符,然后再补一个'\0’
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
★★2.5.3 operator+=(尾插入字符/字符串)
//插入字符
string& operator+=(char ch)
{
//相当于尾插
Push_back(ch);
return *this;
}
//插入字符串
string& operator+=(const char* str)
{
//判断是否需要扩容
int lenth = strlen(str);
if (_size + lenth > _capacity)
{
reserve(_size + lenth);
}
//这里如何处理插入?不建议用strcat,效率低,这里建议直接将strcpy,只要注意尾部拷贝位置!
strcpy(_str + _size, str);
_size += lenth;
return *this;
}
2.5.4 append(从某个字符串截取部分尾插到字符数组中)
//从某个字符串截取部分尾插到字符数组中
string& append(const char* str, size_t pos, size_t len)
{
if (_size + len > _capacity)
(*this).reserve(_size + len);
//截取部分通过循环尾插
for (int i = 0;i < len;i++)
{
Push_back(*(str + pos + i));
}
return *this;
}
2.5.5 insert(指定位置插入字符/字符串 💡需要挪动数据 注重临界!!)
//指定位置插入字符
string& insert(size_t pos, char ch)
{
//判断是否需要扩容
size_t newcapacity = _capacity == _size == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
//挪动数据
int end = _size + 1;
while (end > pos)
{
_str[end] = _str[end-1];
end--;
}
_str[pos] = ch;
_size++;
return *this;
}
//指定位置之后插入字符串
string& insert(const char* str, size_t pos)
{
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;
}
2.5.6 erase(删除指定位置后面指定个数字符)
//删除指定位置后面指定个数字符
string& erase(size_t pos, size_t len)
{
//1.如果指定位置后面字符<len,即删除后面所有
if (len == npos || len >= _size - pos)
{
//只需要将'\0'放到pos位置
_str[pos] = '\0';
_size = pos;
}
//2.后面字符>len,则需要将后面数据挪动到前面
else
{
size_t begin = pos + len;
while (begin <= _size)
{
_str[begin - len] = _str[begin];
begin++;
}
_size -= len;
}
return *this;
}
2.5.6 find (从指定位置向后寻找指定字符)
size_t find(char ch, size_t pos)const
{
assert(pos < _size);
//循环遍历,找到返回下标
for (int i = pos;i < _size;i++)
{
if (_str[i] == ch)
return i;
}
//找不到返回npos
return npos;
}
2.6 string类非成员函数
2.6.1 operator<<(申明为友元函数 !)
ostream& operator<<(ostream& out, const string& s)
{
out << s._str << endl;
return out;
}
★★2.6.2 operator>>(申明为友元函数)
cin>>流提取拿不到空格,空格是分隔符!注意循环结束条件!
很多人会直接用push_back尾插实现一个一个字符插入,但是这样会不断的扩容!效率会很低,我们可以创一个加载数组buffer,先将数据存入buffer中,然后做字符串+=尾插
istream& operator>>(istream& in, string& s)
{
int i = 0;
char buffer[128] = { '\0' };
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
if (i == 127)//+=底层用strcpy,需要'\0',所以这里不能是i==128,这里很细节!!!!
{
s += buffer;
i = 0;
}
buffer[i++] = ch;
ch = in.get();
}
//将buffer剩余数据插入
if(i>=0)
{
buffer[i] = '\0';
s += buffer;
return in;
}
}
2.6.3 getline(可以获取输入的空格,也申明为友元)
istream& getline(istream& in, string& s)
{
int i = 0;
char buffer[128] = { '\0' };
char ch = in.get();
while (ch != '\n')
{
if (i == 127)//+=底层用strcpy,需要'\0'!!!!
{
s += buffer;
i = 0;
}
buffer[i++] = ch;
ch = in.get();
}
buffer[i] = '\0';
s += buffer;
return in;
}
三、总结
作为STL第一个标准类模板,string的用处广泛!很多人会用string的类成员函数,但是不知道它的底层如何实现,我以为如果你能深入了解底层实现原理,在运用函数上可以很好避免错误,能明白它为什么要这么用,传的参数到底是什么?所以我们不仅要知其然,也要知其所以然!这样两者结合,我们运用函数更加得心应手!最后,希望读者能不吝赐教!感谢支持❤❤