目录
一、string类
二、构造函数、拷贝构造函数及析构函数
1、构造函数
2、拷贝构造函数
3、析构函数
三、string类中对容量操作的成员函数
1、size
2、capacity
3、reserve
4、resize
5、clear
6、empty
四、string类中对象的增删查改操作
1、push_back
2、append
3、c_str
4、find
5、substr
6、insert
7、erase
五、string对于重要的运算符重载
1、赋值运算符的重载
2、流插入 <<
3、流提取 >>
4、下标访问 [ ]
5、加等一个字符 +=
6、加等字符串 +=
7、大于 >
8、等于 ==
9、小于 <
10、大于等于 >=
11、小于等于 <=
12、不等于 !=
六、迭代器与范围for
1、迭代器
2、范围for
3、结果对比
七、完整代码
1、 string是表示字符串的字符串类。2、 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。3、 string在底层实际是:basic_string模板类的别名,typedef basic_stringstring。 4、 不能操作多字节或者变长字符的序列。在使用string类时,必须包含#include头文件以及using namespace std。
那么接下来我们就来模仿标准库里的string类来进行一个模拟实现,进而更深层次地理解string容器。 (为了与标准库区分,我们在自己的命名空间里来实现)
首先根据源码,我们可以知道string中的主义成员变量我们可以写成 char* _str(字符数组),size_t _size(数据个数),size_t _capacity(容量大小)。
string的构造函数分为无参构造和有参构造,通过无参构造的对象会被默认生成一个空字符串,因此我们可以带一个缺省值。没有参数时就直接构造一空串。
string(const char* str = "")
:_str(new char[strlen(str) + 1])
, _size(strlen(str))
, _capacity(strlen(str))
{
strcpy(_str, str);
}
注:在开空间时,我们需要多开一个空间,因为strlen算出的大小不包含 ‘\0’ ,因此我们需要给 ‘\0’留一个空间。
拷贝构造函数是默认成员函数,如果不写编译器会自动生成,对于内置类型完成浅拷贝,对于自定义类型调用其拷贝构造函数完成拷贝。对于string类型来说,如果不自己写拷贝构造函数会导致浅拷贝问题。
如果仅仅依靠编译器自己提供的拷贝构造函数,就会像上图一样两个对象指向同一块空间。这就是我们所说的浅拷贝,浅拷贝有两大缺点:1、两个对象在析构时都会调用自己的析构函数,这样同一块空间就会被析构两次;2、一个对象的数据改变,另一个对象的数据也改变了。
因此我们自己实现一个拷贝构造函数尤为重要。我们需要进行深拷贝。
string(const string& str)
:_str(new char[str._capacity+1])
,_size(str._size)
,_capacity(str._capacity)
{
strcpy(_str, str._str);
}
上面的这种实现方法是比较常见的一种写法,但是我们还有一种更加简便的写法。我们可以通过已经实现了的构造函数传一个常量字符串,即下面的 str . _str,来创建一个l临时对象,然后将这个临时对象的成员与自己交换,这样也完成了拷贝构造。
void swap(string& s)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
string(const string& str)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(str._str);
swap(tmp);
}
但是,为了写成这个拷贝构造函数我们还写了一个swap函数,这这么就简便了呢?
那是因为通过查阅标准库我们发现swap函数也是一个string类中提供了的函数,因此我们不仅简便了拷贝构造函数的写法,还又完成了一个函数的实现。并且,因为tmp是一个局部对象,因此在出作用域后就会自动调用析构函数,所以交换后还可以清理掉原来的空间,一举两得。
注:蓝色的swap函数是我们自己写的,而黄色的,前面写明了域的swap函数是C++库中的一个现成的函数。这两个一定要区别开来。
我们可以使用delete直接释放掉 _str的空间。
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
它的功能是返回字符串有效字符的长度。我们可以直接返回其成员变量中的 _size。
size_t size()const
{
return _size;
}
它的功能是返回返回空间总大小 。我们可以直接返回其成员变量中的 _capacity
size_t capacity()const
{
return _capacity;
}
它的功能是为字符串预留空间
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void resize(size_t n, char ch = '\0')
{
if (n > _size)
{
reserve(n);
for (size_t i = _size; i < n; i++)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
else
{
//删除数据
_str[n] = '\0';
_size = n;
}
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
它的作用是检测字符串释放为空串,是返回true,否则返回false
bool empty()const
{
return _size == 0;
}
void push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
在字符串后追加一个字符串
void append(const char* str)
{
size_t len = strlen(str);
if (_capacity < len + _size)
{
reserve(len + _size);
}
strcpy(_str + _size, str);
_size += len;
}
const char* c_str()const
{
return _str;
}
补充:C语言字符串和 string类字符串的区别(下面我们来通过一段代码来看看他们的区别)
void test_string8()
{
string s1("hello");
s1 += '\0';
s1 += "De Bruyne";
cout << s1 << endl;
cout << s1.c_str() << endl;
}
代码运行结果如下:
通过上面的运行结果我们可以看出C语言的字符串一定是通过 ’\0‘ 结束的;而string 的字符串中 ’\0‘ 不一定就是字符串结束的标志。
npos是string类的静态成员变量,静态成员变量要在类外定义的。我们一般将它设为公共权限,并赋值为 -1。
在字符串中寻找一个字符
size_t find(char ch, size_t pos = 0)const
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (ch == _str[i])
{
return i;
}
}
return npos;
}
在字符串中寻找字符串
size_t find(const char* sub, size_t pos = 0)const
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, sub);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
string substr(size_t pos, size_t len = npos)const//取子串
{
assert(pos < _size);
size_t reallen = len;
if (reallen == npos || reallen + pos > _size)
{
reallen = _size - pos;
}
string sub;
for (size_t i = pos; i < reallen + pos; i++)
{
sub += _str[i];
}
return sub;
}
插入一个字符
string& insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
size_t end = size() + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = ch;
_size++;
return *this;
}
插入一个字符串
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = _size + len;
while (pos + len <= end)
{
_str[end] = _str[end - len];
end--;
}
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
void erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
编译器默认生成的赋值重载也会导致浅拷贝,所以我们需要实现深拷贝。
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;
}
和拷贝构造函数一样,我们也可以用简便写法来实现赋值运算符的重载。(注意不要自己给自己赋值)。
string& operator=(const string& str)
{
if (this != &str)
{
string tmp(str);
swap(tmp);
}
return *this;
}
类外定义
ostream& operator<<(ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); i++)
{
out << s[i];
}
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch;
ch = in.get();
const size_t N = 32;
char buff[N];
size_t i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == N - 1)
{
buff[i] = '\0';
s += buff;/*先将输入的字符存在buff数组中,然后一次性+=到s中,这样就避免了输入的
字符串过大,使s频繁扩容的现象。*/
i = 0;
}
ch = in.get();
}
buff[i] = '\0';
s += buff;
return in;
}
普通对象:可读可写
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const对象:可读不可写
char operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
我们可以直接复用push_back来实现一个字符的加等
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
我们可以直接复用append来实现字符串的加等
string& operator+=(const char* str)
{
append(str);
return *this;
}
bool operator>(const string& s)const
{
return strcmp(_str, s._str) > 0;
}
bool operator==(const string& s)const
{
return strcmp(_str, s._str) == 0;
}
bool operator<(const string& s)const
{
return !(_str > s._str) && !(_str == s._str);
}
bool operator>=(const string& s)const
{
return !(_str < s._str);
}
bool operator<=(const string& s)const
{
return !(_str > s._str);
}
bool operator!=(const string& s)const
{
return !(_str == s._str);
}
迭代器作为STL的六大组件之一,它的作用十分重要。而在string中迭代器的本质就是一个 char* 或 const char* 的指针。
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;
}
void test_string9()
{
string s("hello world");
string::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
it++;
}
cout << endl;
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
}
string.h
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include
using namespace std;
#include
#include
namespace zdl
{
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;
}
void swap(string& s)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
//构造函数
string(const char* str = "")
:_str(new char[strlen(str) + 1])
, _size(strlen(str))
, _capacity(strlen(str))
{
strcpy(_str, str);
}
/*string()
:_str(new char[1])
, _size(0)
, _capacity(0)
{
_str[0] = '\0';
}*/
//拷贝构造函数:要深拷贝
string(const string& str)
:_str(new char[str._capacity+1])
,_size(str._size)
,_capacity(str._capacity)
{
strcpy(_str, str._str);
}
/*简便写法 string(const string& str)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(str._str);
swap(tmp);
}*/
//赋值
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;
}
/*简便写法 string& operator=(const string& str)
{
if (this != &str)
{
string tmp(str);
swap(tmp);
}
return *this;
} */
//析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
const char* c_str()const
{
return _str;
}
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
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 == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
void append(const char* str)
{
size_t len = strlen(str);
if (_capacity < len + _size)
{
reserve(len + _size);
}
strcpy(_str + _size, str);
_size += len;
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
string& insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
size_t end = size() + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = ch;
_size++;
return *this;
}
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = _size + len;
while (pos + len <= end)
{
_str[end] = _str[end - len];
end--;
}
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
void erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
size_t find(char ch, size_t pos = 0)const
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (ch == _str[i])
{
return i;
}
}
return npos;
}
size_t find(const char* sub, size_t pos = 0)const
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, sub);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
string substr(size_t pos, size_t len = npos)const//取子串
{
assert(pos < _size);
size_t reallen = len;
if (reallen == npos || reallen + pos > _size)
{
reallen = _size - pos;
}
string sub;
for (size_t i = pos; i < reallen + pos; i++)
{
sub += _str[i];
}
return sub;
}
void resize(size_t n, char ch = '\0')
{
if (n > _size)
{
reserve(n);
for (size_t i = _size; i < n; i++)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
else
{
//删除数据
_str[n] = '\0';
_size = n;
}
}
bool operator>(const string& s)const
{
return strcmp(_str, s._str) > 0;
}
bool operator==(const string& s)const
{
return strcmp(_str, s._str) == 0;
}
private:
char* _str;
size_t _size;
size_t _capacity;
public:
static size_t npos;
};
size_t string::npos = -1;
ostream& operator<<(ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); i++)
{
out << s[i];
}
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch;
ch = in.get();
const size_t N = 32;
char buff[N];
size_t i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == N - 1)
{
buff[i] = '\0';
s += buff;/*先将输入的字符存在buff数组中,然后一次性+=到s中,这样就避免了输入的字符串过大,使s频繁扩容的现象。*/
i = 0;
}
ch = in.get();
}
buff[i] = '\0';
s += buff;
return in;
}
void test_string1()
{
string s1("hello world");
string s2(s1);
string s3;
cout << "s1: " << s1 << endl;
cout << "s2: " << s2 << endl;
cin >> s3;
cout << "s3: " << s3 << endl;
string s4("ZD");
s4 += 'L';
cout << "s4: " << s4 << endl;
s4 += " ";
s4 += "and";
s4 += " ";
s4 += "De Bruyne";
cout << "s4: " << s4 << endl;
}
void test_string2()
{
string s5("hello");
s5.append("ZDL 17");
cout << "s5: " << s5 << endl;
}
void test_string3()
{
string s6("hello");
s6.insert(0, '#');
cout << "s6: " << s6 << endl;
}
void test_string4()
{
string s7("hello");
s7.insert(2, "ZDL");
cout << "s7: " << s7 << endl;
}
void test_string5()
{
string s8("helloeeeeeelllooo");
s8.erase(2, 2);
cout << "s8: " << s8 << endl;
}
void test_string6()
{
string s9("hello world ZDL waiting for love");
string s10 = s9.substr(6, 5);
cout << "s10: " << s10 << endl;
}
void test_string7()
{
string s11("hello world ZDL waiting for love");
string s12("shfshf");
cout << "比较: " << (s11 > s12) << endl;
string s13("De Bruyne");
string s14("De Bruyne");
cout << "s13 与 s14: " << (s13 == s14) << endl;
}
void test_string8()
{
string s1("hello");
s1 += '\0';
s1 += "De Bruyne";
cout << s1 << endl;
cout << s1.c_str() << endl;
}
void test_string9()
{
string s("hello world");
string::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
it++;
}
cout << endl;
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
}
}
test.cpp
#include"string.h"
int main()
{
zdl::test_string1();
cout << "------------------------------" << endl;
zdl::test_string2();
cout << "------------------------------" << endl;
zdl::test_string3();
cout << "------------------------------" << endl;
zdl::test_string4();
cout << "------------------------------" << endl;
zdl::test_string5();
cout << "------------------------------" << endl;
zdl::test_string6();
cout << "------------------------------" << endl;
zdl::test_string7();
cout << "------------------------------" << endl;
zdl::test_string8();
cout << "------------------------------" << endl;
zdl::test_string9();
return 0;
}