前言:为什么要学string类?
回顾C语言学习字符串,比如实现字符串拼接、查找、替换、比较等一些常用操作时,都需要我们手动实现。同时,我们就还需要手动地申请内存,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
C++ 的std::string
是标准库提供的字符串类,封装了字符串的存储、操作和管理逻辑,相比 C 语言,string类可以实现自动内存管理;同时,std::string
有丰富的成员函数,即封装了大量字符串操作接口,无需手动实现复杂逻辑。
在学习之前,先向大家推荐一个好用的文档,方便大家随时去查阅C/C++标准库中的库函数:cplusplus.com - The C++ Resources Network
cplusplus.com - The C++ Resources Network
string类的文档:http://www.cplusplus.com/reference/string/string/?kw=string
1、常用接口说明
1. string类对象的常见构造:
实现类对象的初始化
函数名称 | 功能 |
string() | 构造空的string类对象,即空字符串 |
string(const char* s) | 用字符串s来构造string类对象 |
string(const string&s) | 拷贝构造函数 |
string(size_t n, char c) | 用n个字符c初始化类对象 |
#include<string> // 头文件
using namespace std;
void Test()
{
string st1; // 构造空字符串st1
char ch[] = "Hello World!";
string st2("Hello World!"); // 用"Hello World!"初始化st2
string st3(st2); // 拷贝构造
string st4(st2, 6, 6); // 用st2中第六个字符以后的字符拷贝构造st4
string st5(st2, 6); // 用st2的前6个字符拷贝构造st5
string st6(ch); // 等价于用"Hello World!"初始化st2
string st7(ch, 5); // 用字符串ch的前5个字符初始化st7
string st8(10, 'X'); // 用10和字符'X'初始化st8
}
// 输出:
st2:Hello World!
st3:Hello World!
st4:World!
st5:World!
st6:Hello World!
st7:Hello
st8:XXXXXXXXXX
2. string类对象的容量操作
函数 | 功能 |
s.size() | 返回字符串有效字符长度 |
s.length() | 返回字符串有效字符长度 |
s.capacity() | 返回空间总大小,不包含'\0' |
s.empty() | 检测字符串释放为空串,是返回true,否则返回false |
s.clear() | 清空有效字符 |
s.reserve(n) | 为字符串预留空间,实际预留空间一般大于n |
s.resize (size_t n) resize (size_t n, char c) |
将有效字符的个数改成n个,多出的空间用字符c填充 |
int main()
{
string str("hello world");
cout << str.size() << endl;
cout << str.length() << endl;
cout << str.capacity() << endl;
return 0;
}
3. string类对象的访问及遍历操作
函数 | 功能 |
operator[ ] | 重载运算符[],通过访问数组元素的方式返回pos位置的字符 |
s.begin() | begin获取一个字符的迭代器(地址),用于从前往后遍历字符串。 |
s.end() | end获取最后一个字符下一个位置的迭代器(地址) |
s.rbegin() | rbegin() 返回的是反向迭代器,指向字符串的最后一个字符,用于从后往前遍历字符串。 |
s.rend() | end获取第一个字符前一个位置的迭代器(地址) |
范围for | C++11支持更简洁的范围for的新遍历方式,与auto关键字联用 |
begin()函数有两种形式,分别对应
const
和非const
的std::string
对象:
#include <string>
// 对于非const的std::string对象,返回指向首字符的正向迭代器(可读可写)
std::string::iterator begin();
// 对于const的std::string对象,返回指向首字符的常量正向迭代器(只读)
std::string::const_iterator begin() const;
示例:
void Test02() {
string st2("Hello World!");
string::iterator it1 = st2.begin();
while (it1 != st2.end())
{
cout << *it1 << " ";
++it1;
}
cout << endl;
string::iterator it2 = st2.begin();
// it指针指向的内容可以改变
while (it2 != st2.end())
{
*it2 += 2;
cout << *it2 << " ";
++it2;
}
}
void test01() {
const string st("Hello World!");// 需要使用const_iterator类型
string::const_iterator it1 = st.begin();
while (it1 != st.end())
{
cout << *it1 << " ";
++it1;
}
cout << endl;
//// 错误示范
//string::const_iterator it2 = st.begin();
//// st被const修饰,it指针指向的内容不可以改变
//while (it2 != st.end())
//{
// *it2 += 2;
// cout << *it2 << " ";
// ++it2;
//}
}
auto + 范围for:
int main()
{
string str("Hello World!");
for (auto& e : str)
{
cout << e << " ";
}
return 0;
}
4. string类对象的修改操作
函数 | 功能 |
push_back | 尾插一个字符ch |
append | 尾插一个字符串 |
operator+= | 尾插一个字符串 |
insert | 在指定位置插入字符ch,或者字符串str |
c_str | 返回字符串首地址 |
find | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置,找不到返回npos(整型的最大值) |
rfind | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的 位置,找不到返回npos(整型的最大值) |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
// push_back
string str("hello world");
str.push_back('!');
// append
str.append("!!!!");
str.append("xxxx",2); // 将字符串"xxxx"中的两个字符尾插
str.append(5, 'y'); // 尾插5个字符'y'
str.append("xxxx", 0, 4); // 将字符串"xxxx"从第一个开始往后数四个尾插
// +=
str += '!'; //尾插'!'
// insert
string str("hello world");
str.insert(0, "xxx");
string str1("hds");
str.insert(0, str1);
2、string类的模拟实现
说明:我们依旧采用多文件的方式来模拟实现string类,头文件string.h创建一个string类,并在类里面实现一些需要经常调用的成员函数(如拷贝构造,析构等)以及完成函数的声明;string.cpp文件完成仅在string类中声明的成员函数的实现以及全局函数的实现;test.cpp文件完成对实现的string类的测试。同时,我们将所有的实现过程都放在一个我们自己的命名空间中。
class string {
public:
// ...
private:// 成员变量
char* _str; // 指向字符数组的指针
size_t _size; // 字符串的字符个数
size_t _capacity;// 空间大小(不包含'\0')
static const size_t npos = -1; // 整型的最大值
};
2.1、类对象的初始化
1. 构造函数
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size; //_capacity不包括'\0'
_str = new char[_capacity + 1]; //需要为'\0'开辟一个空间
strcpy(_str, str);
}
使用全缺省参数,当不传参数时,用缺省值(空字符串)来初始化,同时,空字符串默认含有一个 '\0',可以防止将_str初始化为nullptr而对空指针的解引用。
2.拷贝构造函数
string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
拷贝构造,用一个已经存在的对象来初始化一个正在创建的对象。最重要的是对于一些指向了资源的内置类型需要完成深拷贝。
下面我们再来介绍拷贝构造的现代写法:
void swap(string& tmp)
{
std::swap(_str, tmp._str);
std::swap(_size, tmp._size);
std::swap(_capacity, tmp._capacity);
}
string(const string& s)
{
string tmp(s.c_str());
swap(tmp);
}
3.析构函数
~string()
{
if(_str)
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
}
2.2、功能函数接口
1.获取有效数据个数——_size
// 获取有效数据个数
size_t size()const
{
return _size;
}
2.空间大小——_capacity
// 获取空间大小
size_t capacity()const
{
return _capacity;
}
3.访问——重载[ ]
我们要访问pos位置的元素,就要检查pos的合法性,所以,先对pos进行断言。
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
对于const对象,我们只能访问而不能修改,所以,对const对象单独重载一个[ ]运算符。
// const对象
const char& operator[](size_t pos)const
{
assert(pos < _size);
return _str[pos];
}
4.清理数据——clear
void clear()
{
_str[0] = '\0';
_size = 0;
}
5.访问_str——c_str
const char* c_str()const
{
return _str;
}
2.3、尾插——push_back&append&+=
// 头文件函数声明
void reserve(size_t n); // 申请空间
void push_back(char ch); // 尾插一个字符
void append(const char* str); // 尾插字符串
string& operator+=(char ch); // 尾插字符
string& operator+=(const char* str); // 尾插字符串
1、我们要插入数据,首先要检查空间是否足够,如果空间不足,就需要我们扩容。所以,我们先实现string类中的reserve函数,同时,reserve函数能够提前申请空间,减少扩容,提高效率。
void string:: reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1]; // 需要为'\0'开辟一个空间
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
2、push_back
void string::push_back(char ch)
{
if (_size == _capacity)
{
size_t capacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(capacity); // 扩容
_capacity = capacity;
}
_str[_size] = ch; // 尾插
++_size;
_str[_size] = '\0'; // 手动添加'\0'
}
3、append:由于是插入一个字符串,所以在扩容时要考虑字符串长度
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t capacity = (_size + len) > 2 * _capacity ? (_size + len) : 2 * _capacity;
reserve(capacity);
_capacity = capacity;
}
strcpy(_str + _size, str);
_size += len;
}
4、+=
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
2.4、指定位置插入——insert
首先检查pos的合法性,然后检查空间是否足够,最后移动字符串为要插入的字符或者字符串腾出合适的空间并插入。
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
size_t capacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(capacity);
_capacity = capacity;
}
// 将pos位置之后的字符串整体向后移动
size_t end = _size + 1; //将'\0'也向后移动
while (end > pos) {
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
_size++;
}
void string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t capacity = (_size + len) > 2 * _capacity ? (_size + len) : 2 * _capacity;
reserve(capacity);
_capacity = capacity;
}
// 将pos位置的字符串整体向后移动
size_t end = _size+1;
while (end > pos)
{
_str[end+len-1] = _str[end-1];
--end;
}
// 插入字符串
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
}
2.5、查找——find
从pos指向的位置向后查找对应的字符或者字符串是否存在,先对pos断言检查,对于单个字符直接遍历字符串,对于字符串,通过strstr函数来查找。
size_t string::find(char ch, size_t pos)
{
assert(pos < _size);
for (size_t i = 0; i < _size; i++)
{
if (_str[i] == ch)
return i;
}
return npos;
}
size_t string::find(const char* str, size_t pos)
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
return ptr - _str;
}
2.6、比较——<&<=&>&>=&==&!=
我们先实现<和==,然后复用来实现其他的。
bool operator < (const string& str1, const string& str2)
{
return strcmp(str1.c_str(), str2.c_str()) < 0;
}
bool operator <= (const string& str1, const string& str2)
{
return str1 < str2 || str1 == str2;
}
bool operator > (const string& str1, const string& str2)
{
return !(str1 < str2);
}
bool operator >= (const string& str1, const string& str2)
{
return str1 > str2 || str1 == str2;
}
bool operator == (const string& str1, const string& str2)
{
return strcmp(str1.c_str(), str2.c_str()) == 0;
}
bool operator != (const string& str1, const string& str2)
{
return !(str1 == str2);
}
2.7、流提取<< 和流插入>>
流提取<<:通过范围遍历str对象
ostream& operator<<(ostream& out, const string& str)
{
for (auto ch : str)
{
out << ch;
}
return out;
}
流插入>>:为了减少在不断提取的过程中的扩容次数和拷贝构造的次数,提高效率,我们先创建一个一定空间大小数组,将输入的字符先存放到数组中,当字符数组获取到足够的字符后,一次性将字符数组中的元素拷贝到str对象 。
istream& operator>>(istream& in, string& str)
{
str.clear();
const int N = 256; // 创建数组,减少扩容的次数
char buff[N];
int i = 0;
char ch;
ch = in.get(); // 获取输入的第一个字符
while (ch != ' ' && ch != '\n') //字符串分隔符
{
buff[i++] = ch;
if (i == N - 1) //当字符数组获取到足够的元素后,一次性拷贝到str对象
{
buff[i] = '\0';
str += buff;
i = 0;
}
ch = in.get(); // 逐个获取剩下的的字符
}
// 处理还没有提取到str对象中的字符
if (i > 0)
{
buff[i] = '\0';
str += buff;
}
return in;
}
3、完整源码
// 头文件:string.h
#include<iostream>
#include<assert.h>
using namespace std;
namespace hds
{
class string
{
public:
// 迭代器
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin()const
{
return _str;
}
const_iterator end()const
{
return _str + _size;
}
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size; //_capacity不包括'\0'
_str = new char[_capacity + 1]; //需要为'\0'开辟一个空间
strcpy(_str, str);
}
//深拷贝
/*string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}*/
//深拷贝现代写法
void swap(string& tmp)
{
std::swap(_str, tmp._str);
std::swap(_size, tmp._size);
std::swap(_capacity, tmp._capacity);
}
string(const string& s)
{
string tmp(s.c_str());
swap(tmp);
}
// 写法一:
/*string& operator=(const string& s)
{
if (this != &s)
{
delete[] this->_str;
this->_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}*/
// 写法二:
/*string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s._str);
swap(tmp);
}
return *this;
}*/
// 写法三:
string& operator=(string& tmp)
{
swap(tmp);
return *this;
}
~string()
{
if(_str)
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
}
// 获取有效数据个数
size_t size()const
{
return _size;
}
// 获取空间大小
size_t capacity()const
{
return _capacity;
}
// 重载[]
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
// const对象
const char& operator[](size_t pos)const
{
assert(pos < _size);
return _str[pos];
}
//清理_str中的数据
void clear()
{
_str[0] = '\0';
_size = 0;
}
const char* c_str()const
{
return _str;
}
// 尾插
void reserve(size_t n);
void push_back(char ch);
void append(const char* str);
string& operator+=(char ch);
string& operator+=(const char* str);
//指定位置插入
void insert(size_t pos, char ch);
void insert(size_t pos, const char* str);
void erase(size_t pos, size_t len = npos);
//查找
size_t find(char ch, size_t pos = 0);
size_t find(const char* str, size_t pos = 0);
string substr(size_t pos = 0, size_t len = npos);
private:
char* _str;
size_t _size;
size_t _capacity;
static const size_t npos = -1;
};
//声明为全局函数
//比较
bool operator<(const string& str1, const string& str2);
bool operator<=(const string& str1, const string& str2);
bool operator>(const string& str1, const string& str2);
bool operator<=(const string& str1, const string& str2);
bool operator==(const string& str1, const string& str2);
bool operator!=(const string& str1, const string& str2);
//流提取&流插入
ostream& operator<<(ostream& out, const string& str);
istream& operator>>(istream& in, string& str);
}
// 功能实现文件:string.cpp
#include"string.h"
namespace hds
{
void string:: reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1]; // 需要为'\0'开辟一个空间
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void string::push_back(char ch)
{
if (_size == _capacity)
{
size_t capacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(capacity);
_capacity = capacity;
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t capacity = (_size + len) > 2 * _capacity ? (_size + len) : 2 * _capacity;
reserve(capacity);
_capacity = capacity;
}
strcpy(_str + _size, str);
_size += len;
}
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
size_t capacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(capacity);
_capacity = capacity;
}
size_t end = _size + 1; //将'\0'也向后移动
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
_size++;
}
void string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t capacity = (_size + len) > 2 * _capacity ? (_size + len) : 2 * _capacity;
reserve(capacity);
_capacity = capacity;
}
size_t end = _size+1;
while (end > pos)
{
_str[end+len-1] = _str[end-1];
--end;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
}
void string::erase(size_t pos, size_t len) //缺省值只在声明时加
{
assert(pos < _size);
if (len >= _size - pos)
{
_size = pos;
_str[_size] = '\0';
}
else
{
for (size_t i = 0; i <= _size-pos-len; i++)
{
_str[i + pos] = _str[pos+len + i];
}
}
}
size_t string::find(char ch, size_t pos)
{
assert(pos < _size);
for (size_t i = 0; i < _size; i++)
{
if (_str[i] == ch)
return i;
}
return npos;
}
size_t string::find(const char* str, size_t pos)
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
return ptr - _str;
}
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
if (len > _size - pos)
{
len = _size - pos;
}
string sub;
sub.reserve(len);
for (size_t i = 0; i < len; i++)
{
sub += _str[pos + i];
}
return sub;
}
bool operator < (const string& str1, const string& str2)
{
return strcmp(str1.c_str(), str2.c_str()) < 0;
}
bool operator <= (const string& str1, const string& str2)
{
return str1 < str2 || str1 == str2;
}
bool operator > (const string& str1, const string& str2)
{
return !(str1 < str2);
}
bool operator >= (const string& str1, const string& str2)
{
return str1 > str2 || str1 == str2;
}
bool operator == (const string& str1, const string& str2)
{
return strcmp(str1.c_str(), str2.c_str()) == 0;
}
bool operator != (const string& str1, const string& str2)
{
return !(str1 == str2);
}
ostream& operator<<(ostream& out, const string& str)
{
for (auto ch : str)
{
out << ch;
}
return out;
}
istream& operator>>(istream& in, string& str)
{
str.clear();
const int N = 256; // 创建数组,减少扩容的次数
char buff[N];
int i = 0;
char ch;
ch = in.get();
while (ch != ' ' && ch != '\n') //字符串分隔符
{
buff[i++] = ch;
if (i == N - 1)
{
buff[i] = '\0';
str += buff;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
buff[i] = '\0';
str += buff;
}
return in;
}
}
// 测试文件:test.cpp
#include"string.h"
namespace hds
{
void test01()
{
hds::string str("hello world");
cout << str.c_str() << endl;
cout << str.size() << endl;
str.push_back('!');
cout << str.c_str() << endl;
}
void test02()
{
hds::string str("hello");
str.append("world");
cout << str.c_str() << endl;
}
void test03()
{
hds::string str("hello");
str += "world";
cout << str.c_str() << endl;
}
void test04()
{
hds::string str("hello world");
str.insert(0, 'x');
str.insert(7, 'y');
str.insert(13, '!');
cout << str.c_str() << endl;
}
void test05()
{
hds::string str("hello world");
str.insert(0, "***");
str.insert(9, "hds");
str.insert(17, "!!!");
cout << str.c_str() << endl;
}
void test06()
{
hds::string s1("hello world");
s1.erase(5, 10);
cout << s1.c_str() << endl;
hds::string s2("hello world");
s2.erase(5);
cout << s2.c_str() << endl;
hds::string s3("hello world");
s3.erase(6, 3);
cout << s3.c_str() << endl;
}
void test07()
{
hds::string s1("hello world");
cout << s1.find('o', 0) << endl;
cout << s1.find("wor", 0) << endl;
string tmp = s1.substr(6, 10);
cout << tmp.c_str() << endl;
}
void test08()
{
hds::string s1("hello world");
hds::string s2("hello world");
cout << (s1 <= s2) << endl;
}
void test09()
{
string s1("hello world");
string s2("hello world");
cout << s1 << s2 << endl;
string s3;
cin >> s3;
cout << s3 << endl;
}
void test()
{
string s1("hello world");
string s2(s1);
cout << s1 << endl;
cout << s2 << endl;
string s3 ("hds");
s3 = s1;
cout << s3 << endl;
}
}
int main()
{
//hds::test01();
//hds::test02();
//hds::test03();
//hds::test04();
//hds::test05();
//hds::test06();
hds::test();
return 0;
}
本期的分享就到此结束,大家快去上手练习吧!!!如果觉得还不错,请大家点个赞支持一下吧(笔芯笔芯)。