目录
[2、流插入 <<](#2、流插入 <<)
[3、流提取 >>](#3、流提取 >>)
[4、下标访问 []](#4、下标访问 [])
[5、加等一个字符 +=](#5、加等一个字符 +=)
[6、加等字符串 +=](#6、加等字符串 +=)
[7、大于 >](#7、大于 >)
[8、等于 ==](#8、等于 ==)
[9、小于 <](#9、小于 <)
[10、大于等于 >=](#10、大于等于 >=)
[11、小于等于 <=](#11、小于等于 <=)
[12、不等于 !=](#12、不等于 !=)
一:string类
首先先定义一个string类
cpp
class string
{
public:
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
};
string的底层是一个个字符,所以定义一个_str 记录字符,_size用来记录这个字符串的长度,_capacity用来记录开了多少空间。
二:构造函数、拷贝构造函数及析构函数
1、构造函数
string的构造函数分为无参构造和有参构造,通过无参构造的对象会默认生成一个空字符串,因此我们可以带一个缺省值,在没有参数传递时就直接构造一空字符串
cpp
string(const char* str = "")
:_str(new char[strlen(str) + 1])
, _size(strlen(str))
, _capacity(strlen(str))
{
strcpy(_str, str); //将str中的内容拷贝给_str
}
在开空间时,我们需要多开一个空间,因为strlen算出的大小不包括 '\0' ,因此我们需要给 '\0' 留一个空间。
2、拷贝构造函数
拷贝构造函数是默认成员函数,如果不写编译器会自动生成,对于内置类型完成浅拷贝,对于自定义类型调用其构造函数完成拷贝。对于string来说,如果不自己写拷贝构造函数会导致浅拷贝问题。
浅拷贝会使得两个对象指向同一块空间,两个对象在析构时都会调用自己的析构函数,这样同一块空间就会被析构两次;浅拷贝一个对象的数据改变,另一个对象的数据也改变,因为它们指向同一块空间地址。 一个对象被删除后,另一个对象无效
cpp
//浅拷贝实例
class ShallowCopyExample {
public:
int* data;
ShallowCopyExample(int value) {
data = new int(value);
}
// 默认的拷贝构造函数是浅拷贝
ShallowCopyExample(const ShallowCopyExample& other) = default;
~ShallowCopyExample() {
delete data;
}
};
int main() {
ShallowCopyExample obj1(10);
ShallowCopyExample obj2 = obj1; // 浅拷贝
// 问题:obj1和obj2的data指针指向同一内存
// 当其中一个对象析构后,另一个对象的指针就悬空了
}
拷贝构造函数
cpp
string(const string& str)
:_str(new char[str._capacity+1])
,_size(str._size)
,_capacity(str._capacity)
{
strcpy(_str, str._str);
}
上面的这种写法是比较常见的一种写法,但是我们还有一种更加简便的写法。我们可以通过已经实现了的构造函数传一个常量字符串,即下面的str._str,来创建一个临时对象,然后将这个临时对象的成员与自己交换,这样也完成了拷贝构造。
cpp
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string(const string& str)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
//创建一个临时对象
string tmp(str._str);
swap(tmp);
}
但是,为了写成这个拷贝构造函数我们还写了一个swap函数,这这么就简便了呢?
那是因为通过查阅标准库我们发现swap函数也是一个string类中提供了的函数,因此我们不仅简便了拷贝构造函数的写法,还又完成了一个函数的实现。并且,因为tmp是一个局部对象,因此在出作用域后就会自动调用析构函数,所以交换后还可以清理掉原来的空间,一举两得。
3、析构函数
我们可以使用delete直接释放掉_str的空间
cpp
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
三、实现string中对容量操作的成员函数
1、size
size返回字符串有效字符的长度。我们可以直接返回其成员变量中的_szie。
cpp
size_t size() const
{
return _size;
}
2、capacity
capacity返回空间总大小。我们可以直接返回其成员变量中的_capacity。
cpp
size_t capacity() const
{
return _capacity;
}
3、reserve
reserve为字符串预留空间
cpp
void reserve(size_t n)
{
if (n > _capacity)
{
//创建一个临时变量,开n + 1个空间
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str; //将原空间删除
_str = tmp; // 将临时变量的空间给str
_capacity = n; // 预留多少字符给_capacity
}
}
4、resize
resize 的功能是将有效字符的个数改成 n 个,多出的空间用字符 ch 填充。
_size < n < _capacity 先预留n大小的空间,直接用字符 填充 n - size 之间的位置,记住要在最后加上'\0'。
_size > n 直接将 n 的位置置换成'\0'。
n > _capacity 先预留 n 大小的空间,剩下的空间用字符 ch 填充,记住要在最后加上'\0'。
cpp
void resize(size_t n, char ch)
{
if (n > _size)
{
//先检查要不要扩容
reserve(n);
for (size_t i = _size; i < n; i++)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
else
{
_str[_size] = '\0';
_size = n;
}
}
5、clear
作用是清空有效字符
cpp
void clear()
{
_str[0] = '\0';
_size = 0;
}
6、empty
empty的作用是检测字符串释放为空字符串,是返回true,否则返回false。
cpp
bool empty() const
{
return _size == 0;
}
四、string类中对象的增删查改操作
1、push_back
在字符串后面插字符c
cpp
void string::push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
2、append
在字符串后面追加一个字符串
cpp
void string::append(const char* str)
{
//先计算追加字符的个数
size_t len = strlen(str);
//如果 len + _size > _capacity 就需要扩容
if (_capacity < len + _size)
{
//重新申请预留空间
reserve(len + _size);
}
//从_str + _size出位置开始复制
strcpy(_str + _size, str);
_size += len;
}
3、c_str
返回一个C风格的字符串
cpp
char* string::c_str() const
{
return _str;
}
C风格字符串和string类字符串的区别
cpp
void test2()
{
string s1("hello");
s1 += '\0';
s1 += "dafdsaf";
cout << s1 << endl;
cout << s1.c_str() << endl;
}
上述代码的执行结果是:

C风格字符串以 \0 结尾, 但是string类字符串不一定以 \0 结尾,依赖size()/lenth()。
4、find
在字符串中寻找一个字符,返回第一次出现的下标
npos是string类的静态成员变量,静态成员变量要在类外定义的。我们一般将它设为公共权限,并赋值为-1。
cpp
size_t string::find(const char sub, size_t pos) const
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == sub)
return i;
}
return npos;
}
在字符串中找字符串
cpp
size_t string::find(const char* sub, size_t pos) const
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, sub);
if (ptr == nullptr)
{
return npos;
}
else
{
//指针-指针就是指针之间的距离
return ptr - _str;
}
}
5、substr
它的作用是在str中从pos位置开始,截取n个字符,然后将其返回。如果不传len,则默认截取从pos开始到结尾的全部字符。 如果 pos + n > _size, 说明要截取的字符大于str的长度,则截取从pos开始到结尾的全部字符。
cpp
string substr(size_t pos = 0, size_t len = npos) const;
string string::substr(size_t pos, size_t len) const
{
assert(pos < _size);
size_t reallen = len;
//如果从pos开始截取n个字符大于_size的长度,则截取这之间的全部字符
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;
}
6、insert
在指定位置前插入一个字符
cpp
string& insert(size_t pos, char ch);
string& 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)
{
//把pos后面的字符往后挪
_str[end] = _str[end - 1];
end--;
}
_str[pos] = ch;
_size++;
return *this;
}
在指定位置前插入一个字符串
cpp
string& string::insert(size_t pos, const char* ch)
{
assert(pos <= _size);
//计算要插入的长度
size_t len = strlen(ch);
//如果要插入的长度加上原本的长度大于_capacity就需要扩容
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = _size + len;
while (pos + len <= end)
{
_str[end] = _str[end - len];
end--;
}
//从pos位置开始复制要插入长度个字符
strncpy(_str + pos, ch, len);
_size += len;
return *this;
}
7、erase
从pos开始删除n个字符
cpp
void erase(size_t pos, size_t len = npos);
void string::erase(size_t pos, size_t len)
{
assert(pos < _size);
if (len == npos || len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
//从pos位置开始复制
strcpy(_str + pos, _str + pos + len);
//_size减了,对应_str字符数也少了
_size -= len;
}
}
五、string中重要的运算符重载
1、赋值运算符的重载
编译器默认生成的赋值重载也会导致浅拷贝,所以我们需要实现深拷贝。
cpp
string& 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;
}
和拷贝构造函数一样,我们也可以用简便写法来实现赋值运算符的重载
cpp
string& string::operator=(const string& str)
{
if (this != &str)
{
string tmp(str);
swap(tmp);
}
return *this;
}
2、流插入 <<
流插入和流提取都要在类外定义
cpp
ostream& operator<<(ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); i++)
{
out << s[i];
}
return out;
}
3、流提取 >>
cpp
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;
i = 0;
}
ch = in.get();
}
buff[i] = '\0';
s += buff;
}
4、下标访问 []
普通对象:可读可写
cpp
char& string::operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const对象:可读不可写
cpp
char& string::operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
5、加等一个字符 +=
我们可以直接复用push_back来实现一个字符的加等
cpp
string& string::operator+=(char str)
{
push_back(str);
return *this;
}
6、加等字符串 +=
我们可以直接复用append来实现字符串的加等
cpp
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
7、大于 >
cpp
bool string::operator>(const string& s) const
{
return strcmp(_str, s._str) > 0;
}
strcmp通过比较两个字符串,如果s1 > s2,返回大于1的值;如果s1 < s2,返回小于1的值;如果s1 == s2,返回0;
8、等于 ==
cpp
bool string::operator==(const string& s) const
{
return strcmp(_str, s._str) == 0;
}
9、小于 <
cpp
bool string:: operator<(const string& s) const
{
return !(_str > s._str) && !(_str == s._str);
}
10、大于等于 >=
cpp
bool string::operator>=(const string& s) const
{
return !(_str < s._str);
}
11、小于等于 <=
cpp
bool string::operator<=(const string& s) const
{
return !(_str > s._str);
}
12、不等于 !=
cpp
bool string::operator!=(const string& s) const
{
return !(_str == s._str);
}
六、迭代器
迭代器作为STL的六大组件之一,它的作用十分重要。而在string中迭代器的本质就是一个char*或const char*的指针。
cpp
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
const_iterator begin() const
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator end() const
{
return _str + _size;
}
最后完整代码
string.h
cpp
#pragma once
#include <iostream>
#include <assert.h>
//#include <string>
#include <string.h>
using namespace std;
namespace meng
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
const_iterator begin() const
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator end() const
{
return _str + _size;
}
string& operator+=(char str);
string& operator+=(const char* str);
string& operator=(const string& str);
char& operator[](size_t pos);
char& operator[](size_t pos) const;
bool operator>(const string& s) const;
bool operator<(const string& s) const;
bool operator==(const string& s) const;
bool operator>=(const string& s) const;
bool operator<=(const string& s) const;
bool operator!=(const string& s) const;
//
//
string(const char* str = "")
:_str(new char[strlen(str) + 1])
, _size(strlen(str))
, _capacity(strlen(str))
{
strcpy(_str, str); //将str中的内容拷贝给_str
}
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string(const string& str)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
//创建一个临时对象
string tmp(str._str);
swap(tmp);
}
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
void reserve(size_t n);
void resize(size_t n, char ch);
void clear()
{
_str[0] = '\0';
_size = 0;
}
bool empty() const
{
return _size == 0;
}
void push_back(char ch);
void append(const char* str);
char* c_str() const;
//找一个字符串
size_t find(const char* sub, size_t pos = 0) const;
size_t find(const char sub, size_t pos = 0) const;
string substr(size_t pos = 0, size_t len = npos) const;
//insert
//在指定位置前插入一个字符
string& insert(size_t pos, char ch);
//插入一个字符串
string& insert(size_t pos, const char* ch);
void erase(size_t pos, size_t len = npos);
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
static const size_t npos;
};
ostream& operator<<(ostream& out, const string& s);
istream& operator>>(istream& in, string& s);
}
string.c
cpp
#define _CRT_SECURE_NO_WARNINGS
#include "string.h"
namespace meng
{
const size_t string::npos = -1;
//运算符的重载
string& string::operator+=(char str)
{
push_back(str);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
//string& 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& string::operator=(const string& str)
{
if (this != &str)
{
string tmp(str);
swap(tmp);
}
return *this;
}
char& string::operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
char& string::operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
bool string::operator>(const string& s) const
{
return strcmp(_str, s._str) > 0;
}
bool string:: operator<(const string& s) const
{
return !(_str > s._str) && !(_str == s._str);
}
bool string::operator==(const string& s) const
{
return strcmp(_str, s._str) == 0;
}
bool string::operator>=(const string& s) const
{
return !(_str < s._str);
}
bool string::operator<=(const string& s) const
{
return !(_str > s._str);
}
bool string::operator!=(const string& s) const
{
return !(_str == s._str);
}
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;
i = 0;
}
ch = in.get();
}
buff[i] = '\0';
s += buff;
}
//
//
void string::reserve(size_t n)
{
if (n > _capacity)
{
//创建一个临时变量,开n + 1个空间
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str; //将原空间删除
_str = tmp; // 将临时变量的空间给str
_capacity = n; // 预留多少字符给_capacity
}
}
void string::resize(size_t n, char ch)
{
if (n > _size)
{
//先检查要不要扩容
reserve(n);
for (size_t i = _size; i < n; i++)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
else
{
_str[_size] = '\0';
_size = n;
}
}
void string::push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
void string::append(const char* str)
{
//先计算追加字符的个数
size_t len = strlen(str);
//如果 len + _size > _capacity 就需要扩容
if (_capacity < len + _size)
{
//重新申请预留空间
reserve(len + _size);
}
//从_str + _size出位置开始复制
strcpy(_str + _size, str);
_size += len;
}
char* string::c_str() const
{
return _str;
}
size_t string::find(const char sub, size_t pos) const
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == sub)
return i;
}
return npos;
}
size_t string::find(const char* sub, size_t pos) const
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, sub);
if (ptr == nullptr)
{
return npos;
}
else
{
//指针-指针就是指针之间的距离
return ptr - _str;
}
}
string string::substr(size_t pos, size_t len) const
{
assert(pos < _size);
size_t reallen = len;
//如果从pos开始截取n个字符大于_size的长度,则截取这之间的全部字符
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& 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)
{
//把pos后面的字符往后挪
_str[end] = _str[end - 1];
end--;
}
_str[pos] = ch;
_size++;
return *this;
}
string& string::insert(size_t pos, const char* ch)
{
assert(pos <= _size);
//计算要插入的长度
size_t len = strlen(ch);
//如果要插入的长度加上原本的长度大于_capacity就需要扩容
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = _size + len;
while (pos + len <= end)
{
_str[end] = _str[end - len];
end--;
}
//从pos位置开始复制要插入长度个字符
strncpy(_str + pos, ch, len);
_size += len;
return *this;
}
void string::erase(size_t pos, size_t len)
{
assert(pos < _size);
if (len == npos || len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
//从pos位置开始复制
strcpy(_str + pos, _str + pos + len);
//_size减了,对应_str字符数也少了
_size -= len;
}
}
}
test.c
cpp
#define _CRT_SECURE_NO_WARNINGS
#include "string.h"
void test()
{
string str("hello world");
for (auto e : str)
cout << e << ' ';
cout << endl;
//string::iterator it = str.begin()
auto it = str.begin();
while (it != str.end())
{
cout << *it << ' ';
++it;
}
cout << endl;
for (auto e : str)
{
e += 2;
cout << e << ' ';
}
cout << endl;
cout << str << endl;
//string::reverse_iterator a = str.rbegin();
auto a = str.rbegin();
while (a != str.rend())
{
cout << *a << ' ';
++a;
}
cout << endl;
}
namespace meng
{
void test1()
{
string s1("hello world");
cout << s1.size() << " " << s1.capacity() << endl;
s1.reserve(16);
cout << s1.capacity() << endl;
//s1.resize(20, 'h');
//cout << s1.c_str() << endl;
//cout << s1.size() << " " << s1.capacity() << endl;
//cout << s1.empty() << endl;
s1.push_back('h');
cout << s1.c_str() << endl;
cout << s1.size() << " " << s1.capacity() << endl;
s1.append("xxxxx");
cout << s1.size() << " " << s1.capacity() << endl;
cout << s1.c_str() << endl;
cout << s1.find("xxx", 0) << endl;
cout << s1.find('o', 0) << endl;
string s2 = s1.substr(4, 5);
cout << s2.c_str() << endl;
string s3 = s1.insert(3, 'a');
cout << s3.c_str() << endl;
string s4 = s1.insert(3, "aaaaaa");
cout << s4.c_str() << endl;
s4.erase(3, 6);
cout << s4.c_str() << endl;
string s5 = s1; //拷贝构造
cout << s5.c_str() << endl;
cout << s1 << endl;
cout << s4 << endl;
cout << (s4 == s1) << endl;
cout << (s4 != s1) << endl;
cout << (s4 > s1) << endl;
cout << (s4 < s1) << endl;
}
}
void test2()
{
string s1("hello");
s1 += '\0';
s1 += "dafdsaf";
cout << s1 << endl;
cout << s1.c_str() << endl;
}
int main()
{
//test();
meng::test1();
//test2();
return 0;
}