目录
1 string的自己实现源码
下面是string的主要部分的代码实现:
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
namespace kl
{
class String
{
const int InitCapacity = 16;
public:
typedef char* iterator;
typedef const char* const_iterator;
//对象的访问及其遍历
const_iterator begin()const
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator end()const
{
return _str + _size;
}
char& operator[](size_t pos)
{
return *(_str + pos);
}
const char& operator[](size_t pos)const
{
return *(_str + pos);
}
//构造函数
String(const char* str = "")
:_str(nullptr)
, _size(0)
,_capacity(InitCapacity)
{
int len = strlen(str);
if (len + 1 > _capacity)
{
_capacity = len;
}
_str = new char[_capacity];
memcpy(_str, str, _capacity);
_size = len;
}
//这跟是验证当时为什么char类型不可以直接隐式类型转化成string类型,原来是string里面没有其构造函数
//char*可以是因为其有拷贝构造
/*String(const char str)
:_str(nullptr)
, _size(1)
, _capacity(InitCapacity)
{
_str = new char[1];
_str[0] = str;
}*/
//迭代器构造
template <class InputIterator>
String(InputIterator first, InputIterator last)
{
_str = new char[_capacity];
while (first != last)
{
push_back(*first);
first++;
}
//_size = strlen(_str);
}
void swap(String& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
//拷贝构造函数 传统写法
//String(const String& s)
//{
// _str = new char[strlen(s._str) + 1];
// //拷贝
// memcpy(_str, s._str, sizeof(char) * strlen(s._str) + 1);
// _size = s._size;
// _capacity = s._capacity;
//}
String(const String& s)
:_str(nullptr)//这个东西一定要写上,如果不写上,后面一定会出现内存越界的问题
{ //因为在拷贝之前的时候,要拷贝的新对象还没有进行初始化,没有准备好
String tmp(s._str);
this->swap(tmp);
}
//拷贝构造函数 现代写法
//赋值重载 传统写法
String& operator=(const String& s)
{
if (*this != s)
{
_str = new char[strlen(s._str) + 1];
//拷贝
memcpy(_str, s._str, sizeof(char) * strlen(s._str) + 1);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
//赋值重载 现代写法
/*String& operator=(String s)
{
swap(s);
return *this;
}*/
//对象的容量操作
int size()
{
return _size;
}
int capacity()
{
return _capacity;
}
bool empty()
{
return size() == 0 ? true : false;
}
void clear()
{//clear不改变容量
_str[0] = '\0';
_size = 0;
}
void reserve(size_t n = 0)
{
if (n > _capacity)
{
//扩容
char *tmp = new char[_capacity * 2];
memcpy(tmp, _str, sizeof(char) * _capacity * 2);
delete _str;
_str = tmp;
//reserve中的_size不需要改变
_capacity = _capacity * 2;
}
}
void resize(size_t n = 0, char ch = '\0')
{
if (n < _size)
{
_str[n] = '\0';
//改变_size的大小
_size = n;
}
else
{//如果_size < 参数 在元素的后面补上参数
reserve(n);
for (int i = _size; i < n; i++)
{
_str[i] = ch;
}
//加上终止符号,防止打印的时候出现乱码
_str[n] = '\0';
_size = n;
}
}
//对象的修改操作
const char* c_str()const
{
return _str;
}
iterator begin()
{
return _str;
}
void push_back(char ch)
{
if (_size == _capacity)
{
// 2倍扩容
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
//在字符串尾部插入字符串
String& append(const char* s)
{
int len = strlen(s);
if (_size + len > _capacity)
{//扩容
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
for (int i = 0; i < len; i++)
{
_str[_size++] = s[i];
}
return *this;
}
//插入的时候一定要从后往前移
String& insert(size_t pos, size_t n, char c)
{
assert(pos < _size);
if (pos + n > _size)
{//扩容
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
int end = pos + n;
int size = _size + n - 1;
while (end >= pos)
{
_str[size--] = _str[end--];
}
for (int i = pos; i < pos + n; i++)
{
_str[i] = c;
}
//更新_size
_size = _size + n;
_str[_size] = '\0';
return *this;
}
String& erase(size_t pos = 0, size_t len = npos)
{
if (pos + len > _size)
{
_size = pos;
_str[_size] = '\0';
}
else
{
int end = pos + len;
while (end <= _size)
{
_str[pos++] = _str[end++];
}
}
_size = _size - len;
_str[_size] = '\0';
return *this;
}
size_t find(char c, size_t pos = 0) const
{
for (int i = 0; i < _size; i++)
{
if (_str[i] == c)
{
return i;
}
}
return -1;
}
size_t find(const char* s, size_t pos = 0) const
{
char* tmp = strstr(_str + pos, s);
if (tmp)
{
return tmp - _str;
}
else
{
return npos;
}
}
size_t find(const String& str, size_t pos = 0) const
{
char* tmp = strstr(_str + pos, str.c_str());
if (tmp)
{
return tmp - _str;
}
else
{
return npos;
}
}
String substr(size_t pos = 0, size_t len = npos)
{
assert(pos < _size);
size_t n = len;
if (len == npos || pos + len > _size)
{
n = _size - pos;
}
kl::String tmp;
tmp.reserve(n);
for (size_t i = pos; i < pos + n; i++)
{
//tmp += _str[i];
tmp.operator+=(_str[i]);
}
return tmp;
}
String& operator+=(const String& s)
{
append(s.c_str());
return *this;
}
bool operator==(const String& s)
{
if (s._size != _size)
{
return false;
}
for (int i = 0; i < _size; i++)
{
if (s[i] != (*this)[i])
{
return false;
}
}
return true;
}
bool operator!=(const String& s)
{
return !operator==(s);
}
bool operator>(const String& s)
{
}
bool operator>=(const String& s)
{
}
bool operator<=(const String& s)
{
}
//析构函数
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
int _size;
int _capacity;
const static size_t npos;
};
//静态成员变量类外初始化
const size_t String:: npos = -1;
}
下面是与库中string的比对测试:
#include "String.h"
void test01()
{
kl::String str("hello world");
kl::String str1 = "linux is not linux";
kl::String str2(str);
//str1 = str2;
cout << str.c_str() << endl;
cout << str1.c_str() << endl;
cout << str2.c_str() << endl;
}
void test02()
{
kl::String s = "12345678999";
//string s = "12345678999";
//auto it = s.begin();
kl::String::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
it++;
}
cout << endl;
for (auto e : s)
{
cout << e << " ";
e++;
}
cout << s.c_str() << endl;
}
void test03()
{
string s = "1234567";
s.erase(2,3);
cout << s.c_str() << endl;
kl::String s1 = "1234567";
s1.erase(2, 3);
cout << s1.c_str() << endl;
//string s1(s.begin(), s.end());
}
void test04()
{
/*string s = "abcdefg";
string s1 = "cg";
int pos = s.find(s1);
cout << pos << endl;
kl::String s2 = "abcdefg";
kl::String s3 = "cg";
int pos2 = s2.find(s3);
cout << pos2 << endl;*/
string str = "12345";
str.insert(2, 2, 'x');
/*str.append("9999");
str += "8888";*/
cout << str.c_str() << endl;
kl::String str2 = "12345";
/*str2.append("9999");
str2 += "8888";*/
str2.insert(2, 2, 'x');
cout << str2.c_str() << endl;
}
int main()
{
test01();
//test02();
//test03();
//test04();
return 0;
}
2 string实现的一些细节
2.1reserve和resize的区别:
用我自己的话来说就是reserve不会改变字符串的_size长度,只会改变capacity容量,而resize两者都会改变,resize函数接口中的参数,如果大于开始的size,这时候会在原始空余的后面加上字符填充(默认是'\0'),这里具体的话可以点击以下链接查看。
链接:STL 中 resize() 和 reserve() 的区别__牛客网
2.2深浅拷贝:
这里出错的原因是string类型中有char*这样一个自定义类型的数据,他会重新开辟一段新的空间,而普通的浅拷贝(也就是值拷贝),这时候str1对象只会简单的把他的那个空间的地址赋给str中的char*,因此两个string对象中的char*指向同一块空间,在他们销毁(调用析构函数的时候)会重复析构,导致后面哪一个析构的指针成了野指针,故而报错。不懂的可以看下我下面的这张图。
2.3 拷贝构造和赋值重载区分:
这里可以具体的看这个string对象是否为新创建的,如果是新创建的调用的就是拷贝构造,反之就是赋值重载。
如图:上面的倒数第二行调用的是构造,最后一行调用的是赋值重载。
2.4传统写法与现代写法:
void swap(String& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
//拷贝构造函数 传统写法
//String(const String& s)
//{
// _str = new char[strlen(s._str) + 1];
// //拷贝
// memcpy(_str, s._str, sizeof(char) * strlen(s._str) + 1);
// _size = s._size;
// _capacity = s._capacity;
//}
String(const String& s)
:_str(nullptr)//这个东西一定要写上,如果不写上,后面一定会出现内存越界的问题
{ //因为在拷贝之前的时候,要拷贝的新对象还没有进行初始化,没有准备好
String tmp(s._str);
this->swap(tmp);
}
//拷贝构造函数 现代写法
//赋值重载 传统写法
String& operator=(const String& s)
{
if (*this != s)
{
_str = new char[strlen(s._str) + 1];
//拷贝
memcpy(_str, s._str, sizeof(char) * strlen(s._str) + 1);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
//赋值重载 现代写法
/*String& operator=(String s)
{
swap(s);
return *this;
}*/
现代写法与传统写法之间的区别无法就是更加简洁了(个人理解),巧妙的运用了swap这个函数,这里我的swap实现可能很多人会有疑问为什么不之间把两个string对象之间交换呢?
void swap(String& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
这是因为,string是自定义类型,自定义类型如果在T tmp = a, a = b, b = tmp。就会导致死循环了
,自定义类型在赋值的时候,他都是要调用operator=这个重载函数的,然重载函数中,在之间调用swap这个函数,试问,这样是不是构成死循环了?
3 自己在实现过程中的一些见解
3.1:接口函数中的形参是否冗余了呢?
如这个函数如一个string s = “1234567”; string s2 = "yyy"; s.append("xxx"); s.append(s2);
当时我没有实现上面一个函数的时候,发现后面的s.append(s2);跑不过,(这里string对象类型不可能转化成char*)后面我把上面的函数给实现了,然后把下面的函数给删除了,两个都可以跑,s.append("xxx");也可以跑的原因是(调用的时候,会发生隐式类型转化“xxx”char*类型会转化为string类型)然后再调用上面的那个函数,我心里想为啥只要一个都可以实现了还有两个函数都实现呢?原来是因为效率的原因。
3.2拷贝构造这跟我没有初始化_char导致的错误:
String(const String& s)
:_str(nullptr)//这个东西一定要写上,如果不写上,后面一定会出现内存越界的问题
{ //因为在拷贝之前的时候,要拷贝的新对象还没有进行初始化,没有准备好
String tmp(s._str);
this->swap(tmp);
}
我遇到的问题是我把那个:_str(nullptr)给屏蔽掉就会报错。这是为什么呢?
首先想想我们如果不置空,会出现什么问题呢?调用拷贝构造函数,肯定是没有构造好的新对象,那么swap的时候就是新对象和tmp进行交换,那_str指向哪里是不确定的,有野指针的风险。
有些人说那为什么调用上面的这个 kl::String str2 = str;函数时没有报错呢,这里可能就是编译器的原因,万一其他编译器不是这样处理的呢,所以我们还要严谨一点,让我们的代码更有移植性。
🆗就说到这样了,这是一个正在努力变强的小菜鸡的一些理解(我也是查看很多资料,问了一些老师才总结滴)!最后,如果有错误理解,也希望大佬可以指正。