目录
关于string类
string类的常用接口
string类常用接口的简单模拟实现
关于string类
string类在cplusplus.com的文档介绍
- 1. string是表示字符串的字符串类
- 2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
- 3. string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator>string;
- 4. 不能操作多字节或者变长字符的序列。
string类的常用接口
void Teststring()
{
string s1; // 构造空的string类对象s1
string s2("hello world"); // 用C格式字符串构造string类对象s2
string s3(s2); // 拷贝构造s3
}
#include <iostream>
using namespace std;
#include <string>
//-----------------------------------------------------------------------
//测试string容量相关接口
//size / capacity / clear / resize
void Teststring()
{
//注意:string类对象支持直接用cin和cout进行输入和输出
string s1("hello world");
cout << s1.size() << endl;
cout << s1.length() << endl;
cout << s1.capacity() << endl;
cout << s1 << endl;
//将s1中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
s1.clear();
cout << s1.size() << endl;
cout << s1.capacity() << endl;
//将s1中的有效字符个数增加到6个,并且使用'*'来进行填充
s1.resize(6, '*');
cout << s1.size() << endl;
cout << s1.capacity() << endl;
cout << s1 << endl;
// 将s1中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充
// 注意此时s中有效字符个数已经增加到15个
s1.resize(15);
cout << s1.size() << endl;
cout << s1.capacity() << endl;
cout << s1 << endl;
//另外,resize也有删除有效字符的功能,但是不会改变底层空间的大小
s1.resize(1);
cout << s1.size() << endl;
cout << s1.capacity() << endl;
cout << s1 << endl;
}
//-----------------------------------------------------------------------
//测试reserve
//1.是否改变string类中间的有效元素个数
//2.当reserve参数小于string底层空间时,是否会将空间缩小
void TestReserve()
{
string s1;
s1.reserve(100);
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.reserve(10);
cout << s1.size() << endl;
cout << s1.capacity() << endl;
}
int main()
{
Teststring();
TestReserve();
return 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不会改变容量大小。
#include <iostream>
using namespace std;
#include <string>
//---------------------------------------------------------------------
// string的遍历及访问
// 1.迭代器:begin()+end() / rbegin()+ rend() ...
// 2.for+operator[]
// 3.范围for:底层实现是使用迭代器,实际上是迭代器的封装 (范围forC++11后才支持)
void Teststring()
{
string s1("hello world");
//1.
string::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it;
++it;
}
cout << endl;
//2.
for (size_t i = 0; i < s1.size(); ++i)
{
cout << s1[i];
}
cout << endl;
//3.
for (auto ch : s1)
{
cout << ch;
}
cout << endl;
}
int main()
{
Teststring();
return 0;
}
#include <iostream>
using namespace std;
#include <string>
//---------------------------------------------------------------------
//string的测试与修改
//1.插入(拼接)方式:push_back / append / operator+=
//2.任意位置插入:insert
//3.查找 / 反向查找:find / rfind
//4.替换字符:replace
//5.截取子串 :substr
//6.读取字符:geline
//7.删除:erase
//8.返回c形式字符串 : c_str
void TestString()
{
string s1("hello world");
//插入一个字符
s1.push_back(' ');
s1.push_back('i');
cout << s1 << endl;
//插入一串字符
s1.append(" ");
s1.append("love");
cout << s1 << endl;
//c++更喜欢使用+=
s1 += ' ';
s1 += "you";
cout << s1 << endl;
}
void TestInsert()
{
string s1("hello");
//任意位置插入
s1.insert(5, 1, ' ');
s1.insert(6, "world");
cout << s1 << endl;
//使用迭代器
s1.insert(s1.begin(), '!');
s1.insert(s1.begin()+1, ' ');
cout << s1 << endl;
}
void TestFind_Replace()
{
//笔试题:将下列所有空格都改成 %20
//思路1.
string str("hello world i love you");
size_t pos = str.find(' ');
while (pos != string::npos)
{
str.replace(pos, 1, "%20");
pos = str.find(' ');
}
cout << str << endl;
//====================================
//优化:
//string str("hello world i love you");
1.提前开空间,减少扩容消耗的效率
//size_t num = 0;
//for (auto ch : str)
//{
// if (ch == ' ')
// {
// num++;
// }
//}
//str.reserve(str.size() + 2 * num);
//size_t pos = str.find(' ');
//while (pos != string::npos)
//{
// str.replace(pos, 1, "%20");
// //2.避免重复访问数据,提高效率
// pos = str.find(' ', pos+3);
//}
//cout << str << endl;
//==================================
//思路2.
//string str("hello world i love you");
//string newStr;
//size_t num = 0;
//for (auto ch : str)
//{
// if (ch == ' ')
// {
// num++;
// }
//}
//newStr.reserve(newStr.size() + 2 * num);
//for (auto ch : str)
//{
// if (ch != ' ')
// {
// newStr += ch;
// }
// else
// {
// newStr += "%20";
// }
//}
//cout << newStr << endl;
}
void TestFind_Substr()
{
//笔试题:获取ulr中的域名
string url("http://www.cplusplus.com/reference/string/");
cout << url << endl;
size_t start = url.find("://");
if (start == string::npos)
{
cout << "invalid url" << endl;
return;
}
start += 3;
size_t finish = url.find('/', start);
string address = url.substr(start, finish - start);
cout << address << endl;
}
void TestRfind_Getline()
{
//笔试题:求字符串最后出现单词的长度
string str;
//注意:为什么这里使用 getline 而不使用 cin
//因为 cin 类似于scanf ,读取字符遇到空格或者\0就会终止,无法读取后面的字符
//使用c++提供了 getline :遇到换行符才会终止读取
getline(cin, str);
size_t pos = str.rfind(' ');
if (pos != string::npos)
{
cout << str.size() - pos - 1 << endl;
}
else
{
//只有一个单词的情况
cout << str.size() << endl;
}
}
void TestErase()
{
string str("hello world");
str.erase(5, 1);
cout << str << endl;
str.erase(5, 10);
cout << str << endl;
str.erase(1);
cout << str << endl;
}
void TestC_str()
{
string str("hello world");
cout << str << endl;
cout << str.c_str() << endl;
cout << str << endl;
cout << (void*)str.c_str() << endl;
str += ' ';
str += '\0';
str += "******";
cout << str << endl;
cout << str.c_str() << endl;
}
int main()
{
TestString();
TestInsert();
TestFind_Replace();
TestFind_Substr();
TestRfind_Getline();
TestErase();
TestC_str();
return 0;
}
- 1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
- 2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。
string类常用接口的简单模拟实现
#include <iostream>
using namespace std;
#include <assert.h>
#include<string>
class _string
{
public:
//模拟实现常用接口
//...
private:
char* _str;
size_t size;
size_t capacity;
static const size_t npos;
};
const size_t string::npos = -1;
注:npos
关于npos ,这里建议类里面声明,类外面定义,不建议在类里面给缺省值。因为成员变量给缺省值是因为会在初始化列表进行初始化,但是strtic修饰的静态成员变量,不能给缺省值,因为静态成员变量存储位置在静态区,属于整个工程。
另外,c++11有一个值得吐槽的地方,就是开了一个特例,如果加const ,那么整型静态成员变量可以给缺省值,如下图:
_string()
:_str(new char[1])
, _size(0)
,_capacity(0)
{
_str[0] = '\0';
}
_string(const char* str)
:_size(strlen(str))
{
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
问:为什么无参的字符串构造函数 _str(new char[1]) 初始化不给 nullptr 要给一个空间且加上[ ]?
答:因为如果给nullptr的话,cout是会对_str进行解引用,这样会导致程序崩溃,所以才会给一个空间。而给[ ]是为了在析构的时候与delete[ ] 保持一致。
问:为什么 _str = new char[_capacity + 1] 中要+1?
答:因为_capacity是容量字符,指的是能够存取多少个有效字符,而vs认为 '\0'属于标识符,不属于有效字符的范畴,所以+1是为了给'\0'预留空间。
- 优化:以上两个函数可以优化为缺省函数
_string(const char* str = "\0")
:_size(strlen(str))
{
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
_string(const _string& str)
:_size(str._size)
,_capacity(str._capacity)
{
_str = new char[_capacity + 1];
strcpy(_str, str._str);
}
2.赋值 =
_string operator=(const _string& str)
{
delete[] _str;
_str = new char[str._capacity + 1];
strcpy(_str, str._str);
_size = str._size;
_size = str._capacity;
return *this;
}
- 上述代码有一个问题,就是程序开始就将_str的空间进行释放,这样可能会导致数据的丢失,所以需要进行优化。
_string operator=(const _string& str)
{
if (this != &str)
{
char* tmp = new char[str._capacity + 1];
strcpy(tmp, str._str);
delete[] _str;
_str = tmp;
_size = str._size;
_capacity = str._capacity;
}
return *this;
}
3.析构函数 ~
~_string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
4.成员访问
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
const char* c_str()
{
return _str;
}
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
6.关系运算符(relational operators)string
bool operator>(const _string& str) const
{
return strcmp(_str, str._str) > 0;
}
bool operator==(const _string& str) const
{
return strcmp(_str, str._str) == 0;
}
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);
}
bool operator!=(const _string& str) const
{
return !(*this == str);
}
7.reserve
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
问:这里为什么要加个 if 进行判断?
答:因为如果不进行判断,当 n < _capacity 的时候,会导致缩容的问题,对程序的安全造成隐患,因此要加个判断,避免出现缩容的情况。
void push_back(char ch)
{
if (_size + 1 > _capacity)
{
reserve(_capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
上述代码有一个隐藏的问题,那就是如果 _capacity 如果为 0 ,这个时候进行push_back,代码就会越界,从而崩溃。解决办法有两个,一是修改一下构造函数,而是进行判断,如果_capacity 为 0 就直接进行赋值。
_string(const char* str = "\0")
:_size(strlen(str))
{
_capacity = _size == 0 ? 3 : _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
void append(const char* str)
{
if (_size + 1 > _capacity)
{
reserve(_capacity * 2);
}
size_t len = strlen(str);
strcpy(_str + _size, str);
_size += len;
}
问:这里为什么不适用strcat?
答:因为 strcat 是自己去寻找 '\0' 的位置,而 _str + len 就是 '\0' 的位置 ,strcpy 函数会把需要拷贝的字符串最后的位置的 '\0'拷贝过来。
_string& operator+=(char ch)
{
push_back(ch);
return *this;
}
_string& operator+=(const char* str)
{
append(str);
return *this;
}
void resize(size_t n, char ch = '\0')
{
if (n <= _size)
{
//保留前n个 - 删除数据
_size = n;
_str[_size] = '\0';
}
else //n > size
{
if (n > _capacity)
{
reserve(n);
}
size_t i = _size;
while (i < n)
{
_str[i++] = ch;
}
_size = n;
_str[_size] = '\0';
}
}
void insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size + 1 > _capacity)
{
reserve(_capacity * 2);
}
size_t end = _size;
while (end >= pos)
{
_str[end + 1] = _str[end];
--end;
}
_str[pos] = ch;
++_size;
}
以上代码有个问题,那就是 end 属于无符号整型,而 pos 也属于无符号整型,这里会造成程序的死循环。即使把 end 置成 int 类型,但是也会发生隐式类型转换,有符号会转换成无符号。也不建议进行强转,所以解决办法的话,就是修改挪动数据的逻辑。
优化:
void insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size + 1 > _capacity)
{
reserve(_capacity * 2);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
++_size;
}
当然,insert 还需要重载插入字符串
void insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_capacity * 2 + len);
}
size_t end = _size + len;
while (end > pos + len - 1)
{
_str[end] = _str[end - len];
--end;
}
strncpy(_str + pos, str, len);
_size += len;
}
这里之所以选择 strncpy 而不是 strcpy ,因为 strcpy 会拷贝字符串结尾的标识符 '\0'。
void erase(size_t pos, size_t len = npos)
{
//尾部直接删除
if (pos + len > _size || len == npos)
{
_str[pos] = '\0';
_size = pos;
}
else //挪动数据
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
void swap(_string& str)
{
std::swap(_str, str._str);
std::swap(_size, str._size);
std::swap(_capacity, str._capacity);
}
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* str, size_t pos = 0)
{
assert(pos <= _size);
char* p = strstr(_str + pos, str); //strstr : 返回第一次指针匹配的位置
if (p == nullptr)
{
return npos;
}
else
{
return p - _str;
}
}
ostream& operator<<(ostream& out, const string& str)
{
for (auto ch : str)
{
out << str;
}
return out;
}
注:因为在类域中存在this指针,而友元函数又会增加耦合度,破环封装,所以这里建议流插入或者流提取的实现不写在类域之中。
istream& operator>>(istream& in, string& str)
{
char ch;
in >> ch;
while (ch != ' ' && ch != '\n')
{
str += ch;
in >> ch;
}
return in;
}
问:为什么如果输入多组字符,中间用空格或者换行隔开的话,编译器只能拿到第一组字符,拿不到后面的字符。
答:因为cin或者sancf读取的时候会默认忽然空格或者换行,不进行识别,默认空格或者换行是多个值之间的间隔。流提取并未在输入中获取字符,而是在缓冲区获取字符,而空格或者换行未进入缓冲区,c++/c 规定,值与值之间的区分必须是空格或者换行,所以输入空格或者换行会被认为是多个字符之间的间隔,不会被cin 或者 scanf 拿到。
优化:
istream& operator>>(istream& in, string& str)
{
str.clear(); //清除掉之前的字符
char ch = in.get(); //get()函数不区分间隔
while (ch != ' ' && ch != '\n')
{
str += ch;
ch = in.get();
}
return in;
}
上述版本只是一个简单的版本,实际实现可能有些复杂
istream& operator>>(istream& in, string& str)
{
str.clear(); //清除掉之前的字符
char ch = in.get(); //get()函数不区分间隔
char buff[128];
size_t i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 127)
{
buff[127] = '\0';
str += buff;
i = 0;
}
ch = in.get();
}
if (i != 0) //防止最后的数据没有添加进去
{
buff[i] = '\0';
str += buff;
}
return in;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <string>
#include <assert.h>
class _string
{
public:
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
//模拟实现
//_string()
// :_str(new char[1])
// , _size(0)
// ,_capacity(0)
//{
// _str[0] = '\0';
//}
_string(const char* str = "\0")
:_size(strlen(str))
{
_capacity = _size == 0 ? 3 : _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
_string(const _string& str)
:_size(str._size)
, _capacity(str._capacity)
{
_str = new char[_capacity + 1];
strcpy(_str, str._str);
}
_string operator=(const _string& str)
{
if (this != &str)
{
char* tmp = new char[str._capacity + 1];
strcpy(tmp, str._str);
delete[] _str;
_str = tmp;
_size = str._size;
_capacity = str._capacity;
}
return *this;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
const char* c_str()
{
return _str;
}
bool operator>(const _string& str) const
{
return strcmp(_str, str._str) > 0;
}
bool operator==(const _string& str) const
{
return strcmp(_str, str._str) == 0;
}
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);
}
bool operator!=(const _string& str) const
{
return !(*this == str);
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void push_back(char ch)
{
if (_size + 1 > _capacity)
{
reserve(_capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
void append(const char* str)
{
if (_size + 1 > _capacity)
{
reserve(_capacity * 2);
}
size_t len = strlen(str);
strcpy(_str + _size, str);
_size += len;
}
_string& operator+=(char ch)
{
push_back(ch);
return *this;
}
_string& operator+=(const char* str)
{
append(str);
return *this;
}
void resize(size_t n, char ch = '\0')
{
if (n <= _size)
{
//保留前n个 - 删除数据
_size = n;
_str[_size] = '\0';
}
else //n > size
{
if (n > _capacity)
{
reserve(n);
}
size_t i = _size;
while (i < n)
{
_str[i++] = ch;
}
_size = n;
_str[_size] = '\0';
}
}
void insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size + 1 > _capacity)
{
reserve(_capacity * 2);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
++_size;
}
void insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_capacity * 2 + len);
}
size_t end = _size + len;
while (end > pos + len - 1)
{
_str[end] = _str[end - len];
--end;
}
strncpy(_str + pos, str, len);
_size += len;
}
void erase(size_t pos, size_t len = npos)
{
//尾部直接删除
if (pos + len > _size || len == npos)
{
_str[pos] = '\0';
_size = pos;
}
else //挪动数据
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
void swap(_string& str)
{
std::swap(_str, str._str);
std::swap(_size, str._size);
std::swap(_capacity, str._capacity);
}
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* str, size_t pos = 0)
{
assert(pos <= _size);
char* p = strstr(_str + pos, str); //strstr : 返回第一次指针匹配的位置
if (p == nullptr)
{
return npos;
}
else
{
return p - _str;
}
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
~_string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
private:
char* _str;
size_t _size;
size_t _capacity;
//static const size_t npos;
static const size_t npos = -1;
};
//const size_t string::npos = -1;
ostream& operator<<(ostream& out, const string& str)
{
for (auto ch : str)
{
out << str;
}
return out;
}
istream& operator>>(istream& in, string& str)
{
str.clear(); //清除掉之前的字符
char ch = in.get(); //get()函数不区分间隔
char buff[128];
size_t i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 127)
{
buff[127] = '\0';
str += buff;
i = 0;
}
ch = in.get();
}
if (i != 0) //防止最后的数据没有添加进去
{
buff[i] = '\0';
str += buff;
}
return in;
}
以上仅代表个人看法,欢迎讨论