目录
1.前言
文章只是把一些实现列举单独列举出来,在文章的完整的代码中,string类实现的接口还是比较丰富的!
本章模拟实现是为了更好的理解string的底层实现!分享个人的学习string的过程,希望能帮助到大家!有错误的地方也希望大家指正!
2.为什么要有string
C++是从C语言当中生长出来的,那么C++可以使用C语言的str系列对字符串操作的库函数,那么为什么C++还有有string类呢?
a.如果大家使用过C语言的字符串系列的库函数,就会发现操作起来成本比较高,比如:需要自己开辟空间,能动态的分配空间,而且容易发生内存越界的问题.
b.C++使用string类来管理字符串更加的符和OOP面向对象的编程思想!
3.什么是string
string是C++管理字符串的类!string是通过顺序表的方式来管理字符串,但是string需要兼容C语言类型的字符串格式,所以我们会看到我实际管理字符串的时候需要额外开辟一个空间来存放'\0'
实现c_str()函数实现对C语言字符串的转换。
string是通过长度size来操作字符串的,通过capacity来动态的管理空间,所以,对string类的描述是:
class string{
private:
size_t _size;
size_t _capacity;
char *_str;}
4.如何模拟实现
4.1构造函数
1.无参:string();
2.传入字符串:string (const char* s);
其实关于无参的和传入字符串的构造函数可以使用缺省值来对两个函数合并,定义为string (const char* s = "");
string(const char* str = "")
{
// 为什么不用初始化列表?
// 避免了初始化列表和属性顺序的耦合!
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
// 为什么要用memcpy?不用strcpy?
// strcpy无法处理"hello\0 world"中间带有\0的字符
memcpy(_str, str, _size + 1);
}
4.2拷贝构造函数
1.拷贝构造:string (const string& str);
1.浅拷贝和深拷贝
浅拷贝:编译器只是将对象中的值进行拷贝。对内置类型(int,char等 ,任何类型的指针)而言,一般浅拷贝就可以满足需求,但是如果对象管理资源,那么只是简单的值进行拷贝会发生问题。
a.多个对象同时指向同一块空间,当一个对象销毁时将会释放资源,而此时,其他的对象并不知道资源已经被释放,它也尝试去释放资源,就会违规访问。
b.个对象同时指向同一块空间,不同的对象去修改数据,那么就不能保证数据的独立性。我们知道进程之间是具有独立性的,保证数据的独立性是进程独立性的重要保障。
深拷贝:重新开辟空间,将原有的数据拷贝到新的空间中。这样就确保了,数据的独立性,也解决了重复去析构的问题。但是深拷贝的代价,会导致效率降低的问题.
2.深浅拷贝图解
3.拷贝构造代码
//现代写法
string(const string& str)
{
//1.C++标准规定,是不进行初始化的!属性会是随机值!
// 随机值是不能delete的
_size = _capacity = 0;
_str = nullptr;
string str_tmp(str._str);
//自定义的swap
swap(str_tmp);
}
void swap(string& str)
{
std::swap(_size, str._size);
std::swap(_capacity, str._capacity);
std::swap(_str, str._str);
}
代码说明:
这里复用了构造函数和析构函数,使用构造函数初始化一个和源拷贝对象(str)的对象(str_tmp),然后和this进行交换,str_tmp是在栈上开辟的空间,出了函数就会调用析构函数,将this开辟的空间释放。
4.3析构函数
//注意将开辟的空间释放即可!
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
4.3运算符重载
1.重载=
//赋值
//现代写法
//string& operator=(string str_tmp)string str_tmp会调用拷贝构造
string& operator=(string str_tmp)
{
swap(str_tmp);
return *this;
}
2.重载[]
注意:这里为什么需要重载两个方法?
首先:string对象:a.一般对象,可修改,b,const修饰的对象,不可修改
const char& operator[](size_t pos) const (后面一个const表示和char& operator[](size_t pos)构成重载,前面的const限定了编译阶段方法的调用!如果尝试对const对象进行修改编译器会报错。)
// 返回后_str还在,那么返回引用
// char& operator[](size_t pos) const
// 这样可以传常量(const string s1;)和变量
// 但是常量修改是不行的!
char& operator[](size_t pos)
{
// 这个地方很妙,pos如为负数,size_t整形提升就会成最大值
// 如果大于_szie就会断言
assert(pos < _size);
return _str[pos];
}
// 这里const(表示const修饰字符串(编译阶段)) char&
// operator[](size_t pos) const(传常量构成函数重载)
const char& operator[](size_t pos) const
{
// 这个地方很妙,pos如为负数,size_t整形提升就会成最大值
// 如果大于_szie就会断言
assert(pos < _size);
return _str[pos];
}
3.重载>>
std::istream& operator>>(std::istream& in, string& str)
{
str.clear();
char ch = in.get();
while (ch == ' ' || ch == '\n')
{
ch = in.get();
}
char buff[128];
int i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 128)
{
buff[i] = '\0';
str += buff;
i = 0;
}
ch = in.get();
}
if (i != 0)
{
buff[i] = '\0';
str += buff;
}
return in;
}
4.重载<<
std::ostream& operator<<(std::ostream& out, const string& str)
{
for (auto ch : str)
{
std::cout << ch;
}
return out;
}
5.完整实现代码
#pragma once
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <utility> //解决问题:'swap' is not a member of 'std'
#include <iostream>
namespace xiYan
{
class string
{
// 模拟实现string?
// 描述:管理字符串,怎么管理,定义什么属性?通过顺序表来管理的!
// 1.char* _str 描述字符串,2._capacity 描述空间的大小 3._size 描述存储字符的长度
private:
size_t _size;
size_t _capacity;
char *_str;
// 组织:
public:
typedef char *iterator;
typedef const char *const_iterator;
// const 构成重载
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
// 1.构造函数
// const char* str = "" ,这里""默认带'\0'
string(const char *str = "")
{
// 为什么不用初始化列表?
// 避免了初始化列表和属性顺序的耦合!
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
// 为什么要用memcpy?不用strcpy?
// strcpy无法处理"hello\0 world"中间带有\0的字符
memcpy(_str, str, _size + 1);
}
// 2.拷贝构造函数
string(const string &str)
{
// 1.C++标准规定,是不进行初始化的!属性会是随机值!
// 随机值是不能delete的
_size = _capacity = 0;
_str = nullptr;
string str_tmp(str._str);
swap(str_tmp);
}
// 赋值
// 现代写法
string &operator=(string str_tmp)
{
swap(str_tmp);
return *this;
}
void swap(string &str)
{
std::swap(_size, str._size);
std::swap(_capacity, str._capacity);
std::swap(_str, str._str);
}
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
// char * c_str() const 这里const修饰能传一个常量也能传一个变量
// 传常量:权限的平移
// 传变量:变量是一个权限的缩写
char *c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _size;
}
// 重载一下[]
// 返回后_str还在,那么返回引用
// char& operator[](size_t pos) const
// 这样可以传常量(const string s1;)和变量
// 但是常量修改是不行的!
char &operator[](size_t pos)
{
// 这个地方很妙,pos如为负数,size_t整形提升就会成最大值
// 如果大于_szie就会断言
assert(pos < _size);
return _str[pos];
}
// 这里const(表示const修饰字符串(编译阶段)) char&
// operator[](size_t pos) const(传常量构成函数重载)
const char &operator[](size_t pos) const
{
// 这个地方很妙,pos如为负数,size_t整形提升就会成最大值
// 如果大于_szie就会断言
assert(pos < _size);
return _str[pos];
}
// 实现对数据的增加!
// 增加要考虑什么?
// 1.数据结构的容量是否够
// 这个方式是开辟一个固定的空间
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_bask(char ch)
{
// if(_size == _capacity)
if (_size == _capacity)
{
// 1.第一次 2.非第一次
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
++_size;
}
// 追加:
// 如何将一个字符串放到数组中,拷贝
void append(const char *str)
{
// 如果有对其规则需要计算
size_t len = _size + strlen(str);
if (len >= _capacity)
{
reserve(len);
}
// 用memcpy会好些
strcpy(_str + _size, str);
_size += len;
}
void insert(size_t pos, size_t n, char ch)
{
assert(pos <= _size);
size_t end = _size;
if (_size + n > _capacity)
{
reserve(_capacity + n);
}
while (pos <= end && end != npos)
{
_str[end + n] = _str[end];
end--;
}
for (int i = 0; i < n; i++)
{
_str[pos + i] = ch;
}
_size += n;
}
void insert(size_t pos, const char *str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_capacity + len);
}
size_t end = _size;
while (pos <= end && end != npos)
{
_str[end + len] = _str[end];
end--;
}
for (int i = 0; i < len; i++)
{
_str[pos++] = *(str++);
}
}
string &erase(size_t pos = 0, size_t len = npos)
{
assert(pos <= _size);
if (len > _size - pos)
{
_str[pos] = '\0';
}
else
{
size_t end = pos + len;
for (int i = pos; i < _size - (pos + len); i++)
{
_str[i] = _str[end++];
}
}
return *this;
}
size_t find(char ch, size_t pos = 0) const
{
assert(pos < _size);
for (int i = 0; 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 *ret = strstr(_str + pos, str);
if (ret != NULL)
{
return ret - _str;
}
return npos;
}
void clear()
{
_str[0] = '\0';
}
string &operator+=(const char *str)
{
append(str);
return *this;
}
string &operator+=(char ch)
{
push_bask(ch);
return *this;
}
// 1.实现substr
string substr(size_t pos = 0, size_t len = npos) const
{
// assert(pos > _size);
string ret;
size_t n = len;
if (n == npos || (n + pos) >= _size)
n = _size - pos;
// ret.reserve(_size - pos);
// 如果len + pos > _size || len = npos 一直取到最后
// reserve中的strcpy(tmp, _str);处理了'\0'
ret.reserve(n);
for (int i = pos; i < n; i++)
{
// ret._str[i - pos] = _str[i];
// ret._size++;
ret += _str[i];
}
// if(n != npos && (n < _size))
// {
// ret._str[_size] = '\0';
// }
return ret;
}
//实现小于(或者大于)等于就可以实现下面 > >= <= !=
bool operator<(const string &str) const
{
int n = _size < str._size ? _size : str._size;
//strncmp(_str, str._str, n) 目标字符串是否 <
int ret = strncmp(_str, str._str, n);
// 不会转换,也是输出0,1,-1
return ret == 0 ? _size < str._size: ret < 0;
// 那我直接返回ret不就行了!
// 直接返回ret可不行哦!
//return ret;
}
bool operator==(const string &str) const
{
return _size == str._size && strncmp(_str, str._str, _size) == 0;
}
bool operator>(const string &str) const
{
return !(*this < str || *this == str);
}
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);
}
const static size_t npos;
};
std::ostream &operator<<(std::ostream &out, const string &str)
{
for (auto ch : str)
{
std::cout << ch;
}
return out;
}
std::istream &operator>>(std::istream &in, string &str)
{
str.clear();
char ch = in.get();
while (ch == ' ' || ch == '\n')
{
ch = in.get();
}
char buff[128];
int i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 128)
{
buff[i] = '\0';
str += buff;
i = 0;
}
ch = in.get();
}
if (i != 0)
{
buff[i] = '\0';
str += buff;
}
return in;
}
const size_t string::npos = -1;
};