string是表示字符串的字符串类,该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。. string在底层实际是:basic_string模板类的别名,typedef basic_string string,在使用string类时,必须包含#include头文件以及using namespace std;
本篇文章将重点讲解string类的使用和模拟实现
1.string类常用接口说明
1.1string类对象的常见构造
string() | 构造空的string类对象,即空字符串 |
string(const char*s) | 用C-string来构造string类对象 |
string(size_t n,char c) | string类对象中包含n个字符C |
string(const string& s) | 拷贝构造 |
string(const char* s,size_t n) | 用字符串s的前n个字符来构造 |
1.2string类对象的容量操作
size() | 返回字符串的有效字符长度 |
length() | 返回字符串的有效字符长度 |
capacity() | 返回空间总大小 |
empty() | 检测字符串是否为空,是返回true,否则返回false |
clear() | 清空有效字符 |
reserve(int) | 为字符串预留空间 |
resize(int,char) | 将有效字符的个数该成n个,多出的空间用字符c填充,如果只给了一个参数,默认初始化为'\0' |
注意:
1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一 致,一般情况下基本都是用size()。
2. clear()只是将string中有效字符清空,不改变底层空间大小。
3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的 元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大 小,如果是将元素个数减少,底层空间总大小不变。
4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于 string的底层空间总大小时,reserver不会改变容量大小。
1.3string类对象的访问及遍历操作
1.operator[]
string str = "hello word";
int size = str.size();
for (int i = 0; i < size; i++)
{
//str.operator[](i);
cout << str[i] << endl;
}
2.迭代器
正向迭代器
string str = "hello word";
string::iterator it = str.begin();//得到一个指向首元素的指针
while (it != str.end())//str.end()得到一个指向'\0'的指针
{
cout << *it << endl;
it++;
}
反向迭代器
string str = "hello word";
string::reverse_iterator it = str.rbegin();//得到一个指向最后一个有效元素的指针
while (it != str.rend())//str.rend()得到一个指向首元素前一个的指针
{
cout << *it << endl;
it++;
}
string::iterator得到的是可读可写的指针,可以通过该指针修改原来的字符串
如果想保护原来的字符数,只读不让写string::const_iterator
为了方便,我们可以用auto的方式来自动识别类型
3.范围for
string str = "hello word";
for (auto e : str)
{
cout << e << endl;
}
return 0;
范围for的底层原理还是迭代器
1.4string类对象的修改操作
push_back | 在字符串后尾插字符c |
append | 在字符后追加一个字符串 |
operator+=(最常用的) | 在字符串后追加字符串str |
c_str | 返回C格式字符串 |
find+npos | 从字符串pos位置开始往后找字符c,返回该字符在字符串的位置 |
rfind | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
注意:
1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般 情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。
1.5 string类非成员函数
operator+ | 尽量少用,因为船只返回,导致深拷贝效率低 |
operator>> | 流输入运算符重载 |
operator<< | 流提起运算符重载 |
getline | 获取一行字符串 |
relational operators | 比较运算符重载 |
2.string类模拟实现
2.1构造函数
在标准库中没有对string变量赋初始值的话,会默认是空字符串,开15个空间
string(const char* str = "")
:_size(strlen(str))
, _capacity(15)
{
if (_size>_capacity)
{
_capacity = _size;
}
_str = new char[_capacity + 1];
strcpy(_str, str);
}
2.2 reserve
reserve是string类中更改容量的一个成员函数,准确的来说是一个扩容函数,如果n<capacity那么这个函数将不会做任何改变。
void reserve(size_t n = 0)
{
if (n > _capacity)
{
char* temp = new char[n + 1];//需要多一个空间给'\0'
strcpy(temp, _str);//将原数据拷贝
delete[] _str;
_str = temp;
_capacity = n;
}
}
2.3拷贝构造函数
写拷贝构造函数前需要讲讲深浅拷贝问题
如果没有写拷贝构造函数,那么编译器就会自动生成一个浅拷贝的拷贝构造函数
如果是浅拷贝的话,str1和str2指向的字符串中间是同一片空间,会造成以下几个问题
1.如果str1修改,会影响str2的值,同样str2修改会影响str1的值
2.结束调用析构函数的时候会对同一片空间析构两次
所以需要深拷贝,深拷贝会重新开辟一个空间给str2
//s1(s2)
//传统写法
string(const string& s)
:_size(s._size)
,_capacity(s._capacity)
{
_str = new char[_capacity + 1];
strcpy(_str, s._str);
}
上面这个拷贝构造函数是传统的写法,现在的拷贝构造函数会复用构造函数
void swap(string& s)
{
std::swap(_str, s._str);//用全局的swap函数对string底层的字符串进行交换
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
//现在写法
string(const string& s)
:_size(s._size)
,_capacity(s._capacity)
,_str(nullptr)//需要赋一个空指针,交换后这个指针将会给temp,temp回收后会调用析构函数,没有给初始指针会报错
{
string temp(s._str);
//swap(temp,this) 使用全局的swap函数会调用深拷贝效率低
swap(temp);
}
2.4 operator=()
//s1 = s2
//传统版本
string& operator=(const string& s)
{
if (&s != this)
{
_size = s._size;
_capacity = s._capacity;
_str = new char[_capacity + 1];
strcpy(_str, s._str);
}
return *this;
}
和拷贝构造函数一样,
2.4 c_str()
获得c语言形式的字符串
const char* c_str() const
{
return _str;
}
2.5 size()
获得字符串长度
size_t size()
{
return _size;
}
2.6 capacity()
size_t capacity(const string& s)const
{
return s._capacity;
}
2.7 operator[]()
[]运算符重载,获取字符串中i位置的字符
char& operator[](size_t i)
{
return _str[i];
}
同时需要考虑到只读的情况
const char& operator[](size_t i)const
{
return _str[i];
}
2.8 operator=()
//s1 = s2
//传统版本
string& operator=(const string& s)
{
if (&s != this)
{
char* temp = new char[s._capacity + 1];
strcpy(temp, s._str);
delete[] _str;
_str = temp;
_size = s._size;//最后放在后面,防止temp没申请到空间
_capacity = s._capacity;
}
return *this;
}
同样赋值运算符重载也可以复用构造函数
//现在写法
string& operator=(string s)
{
swap(s);//用临时变量和*this交换
return *this;
}
2.9relational operator
比较函数的实现
比较原则:比较第一个不匹配的字符的大小,如果都匹配就比较字符串的长度
int compare(const string& s)const
{
return std::strcmp(_str, s._str);
}
bool operator<(string& str1, string& str2)
{
if (str1.compare(str2) < 0)
{
return true;
}
return false;
}
bool operator==(string& str1, string& str2)
{
if (str1.compare(str2) == 0)
{
return true;
}
return false;
}
bool operator<=(string& str1, string& str2)
{
if (str1<str2||str1==str2)
{
return true;
}
return false;
}
bool operator>(string& str1, string& str2)
{
return !(str1 <= str2);
}
bool operator>=(string& str1, string& str2)
{
return !(str1 < str2);
}
bool operator!= (string & str1, string & str2)
{
return !(str1 == str2);
}
2.10 insert()
我们就实现两个重载,一个是从pos位置插入一个字符,一个是从pos位置插入字符串
insert函数与顺序表的插入一样,可以参考下面这篇文章
(4条消息) 【数据结构】顺序表_Patrick star`的博客-CSDN博客
//插入一个字符
void insert(size_t pos,char c)
{
assert(pos <= _size);
size_t end = _size+1;
while (end!=pos)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = c;
}
//插入一个字符串
void insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if ((_size + len) > _capacity)
{
reserve(_size + len);
}
size_t begin1 = _size + 1;
size_t begin2 = begin1 + len;
while (begin1 != pos)
{
_str[begin2-1] = _str[begin1-1];
begin1--;
begin2--;
}
strncpy(_str+begin1, str, len);
}
2.11push_back()
尾插一个字符,可以复用insert
void push_back(const char c)
{
insert(_size, c);
}
2.12 append()
尾插一个字符串,同样复用insert
void append(const char* str)
{
insert(_size, str);
}
2.13 operator+=()
复用push_back和append
string& operator+=(const char c)
{
push_back(c);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
2.14 迭代器
string类中的迭代器实际上就是char指针的别名
typedef char* interator;
typedef const char* const_interator;
interator begin()
{
return _str;
}
interator end()
{
return _str+_size;
}
const_interator begin()const
{
return _str;
}
const_interator end()const
{
return _str+_size;
}
当我们使用范围for进行遍历的时候,编译器会自动调用迭代器,所以这也要求我们命名必须规范,如果命名不规范就不能使用范围for进行遍历
2.15 流提取和流插入运算符重载
std::ostream& operator<<(std::ostream& out, string str)
{
//out<<str.c_str 这种形式无法输出\0
for (auto e : str)
{
out << e;
}
}
std::istream& operator>>(std::istream in, string str)
{
str.clear();
char ch = in.get();
char buff[128] = { '\0' };
size_t i = 0;
while (ch != ' ' || ch != '\n')
{
buff[i++] = ch;
if (i == 127)
{
str += buff;
memset(buff, '\0', 128);
i = 0;
}
ch = in.get();
}
str += buff;
return in;
}
3.string类实现总览
#pragma once
#include <iostream>
#include<assert.h>
#include<string.h>
using namespace std;
namespace ldx
{
class string
{
public:
typedef char* interator;
typedef const char* const_interator;
interator begin()
{
return _str;
}
interator end()
{
return _str+_size;
}
string(const char* str = "")
:_size(strlen(str))
, _capacity(15)
{
if (_size>_capacity)
{
_capacity = _size;
}
_str = new char[_capacity + 1];
strcpy(_str, str);
}
s1(s2)
传统写法
//string(const string& s)
// :_size(s._size)
// ,_capacity(s._capacity)
//{
// _str = new char[_capacity + 1];
// strcpy(_str, s._str);
//}
void swap(string& s)
{
std::swap(_str, s._str);//用全局的swap函数对string底层的字符串进行交换
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
//现在写法
string(const string& s)
:_size(s._size)
,_capacity(s._capacity)
,_str(nullptr)//需要赋一个空指针,交换后这个指针将会给temp,temp回收后会调用析构函数,没有给初始指针会报错
{
string temp(s._str);
//swap(temp,this) 使用全局的swap函数会调用深拷贝效率低
swap(temp);
}
~string()
{
if (_str)
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
}
char& operator[](size_t i)
{
return _str[i];
}
const char& operator[](size_t i)const
{
return _str[i];
}
s1 = s2
传统版本
//string& operator=(const string& s)
//{
// if (&s != this)
// {
// char* temp = new char[s._capacity + 1];
// strcpy(temp, s._str);
// delete[] _str;
// _str = temp;
// _size = s._size;//最后放在后面,防止temp没申请到空间
// _capacity = s._capacity;
// }
// return *this;
//}
//现在写法
string& operator=(string s)
{
swap(s);//用临时变量和*this交换
return *this;
}
//插入一个字符
void insert(size_t pos,const char c)
{
assert(pos <= _size);
if (_size == _capacity)
{
size_t newCapacity = _capacity==0? 4:2 * _capacity;
reserve(newCapacity);
}
size_t end = _size+1;
while (end!=pos)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = c;
}
//插入一个字符串
void insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if ((_size + len) > _capacity)
{
reserve(_size + len);
}
size_t begin1 = _size + 1;
size_t begin2 = begin1 + len;
while (begin1 != pos)
{
_str[begin2-1] = _str[begin1-1];
begin1--;
begin2--;
}
strncpy(_str+begin1, str, len);
}
void reserve(size_t n = 0)
{
if (n > _capacity)
{
char* temp = new char[n + 1];//需要多一个空间给'\0'
strcpy(temp, _str);//将原数据拷贝
delete[] _str;
_str = temp;
_capacity = n;
}
}
const char* c_str() const
{
return _str;
}
size_t size()
{
return _size;
}
int compare(const string& s)const
{
return std::strcmp(_str, s._str);
}
size_t capacity(const string& s)const
{
return s._capacity;
}
void push_back(const char c)
{
insert(_size, c);
}
void append(const char* str)
{
insert(_size, str);
}
string& operator+=(const char c)
{
push_back(c);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
void clear()
{
_size = 0;
_str[0] ='\0';
}
private:
char* _str;
size_t _size;
size_t _capacity;//有效字符的容量
};
bool operator<(string& str1, string& str2)
{
if (str1.compare(str2) < 0)
{
return true;
}
return false;
}
bool operator==(string& str1, string& str2)
{
if (str1.compare(str2) == 0)
{
return true;
}
return false;
}
bool operator<=(string& str1, string& str2)
{
if (str1<str2||str1==str2)
{
return true;
}
return false;
}
bool operator>(string& str1, string& str2)
{
return !(str1 <= str2);
}
bool operator>=(string& str1, string& str2)
{
return !(str1 < str2);
}
bool operator!= (string & str1, string & str2)
{
return !(str1 == str2);
}
std::ostream& operator<<(std::ostream& out, string str)
{
//out<<str.c_str 这种形式无法输出\0
for (auto e : str)
{
out << e;
}
}
std::istream& operator>>(std::istream in, string str)
{
str.clear();
char ch = in.get();
char buff[128] = { '\0' };
size_t i = 0;
while (ch != ' ' || ch != '\n')
{
buff[i++] = ch;
if (i == 127)
{
str += buff;
memset(buff, '\0', 128);
i = 0;
}
ch = in.get();
}
str += buff;
return in;
}
}