C++:渴望力量吗,少年?
所谓模拟实现,就是在理解的基础上自己用代码实现相同的功能,所以首先要了解string的特性和相应函数的功能。例如在最开始要考虑的就是string有哪些成员变量,要实现哪些函数,六大默认成员函数是使用默认的还是要自己显式定义……下面的代码已有很详细的注释,就不再另作说明了。
string.h代码如下:
#pragma once
#include
namespace GGYH
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
/*//这里就是C语言的知识点了,这个指针指向的内容不可修改,但是指针本身可以修改,下面内容在main函数中就可以看出区别
//GGYH::string::const_iterator a = "aaaaa";
//a = "bb";
//a++;
*a = '2';//不能修改指向的内容,但是可以修改指针(比如++、--或者指向别的内容)*/
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
/*string()//无参构造函数
:_str(new char[1]{'\0'})//可以这么给,但是也可以利用全缺省和有参构造函数合并在一起写,如下
,_size(0)
,_capacity(0)
{}*/
//注意不能写string(const char* str = nullptr),因为后面会存在指针的解引用(比如strlen和strcpy都会报错)
//也不能写string(const char* str = '\0'),因为左右类型都是不匹配的,可以写成string(const char* str = "\0")
//但是这里是自己给一个斜杠零,string结尾还有一个斜杠零,相当于有两个斜杠零,也没有必要,所以最好的是下面的空串
string(const char* str = "")//用缺省参数代替上面的无参构造(当string没有给值的时候默认是个空串,但是得开一个字节放斜杠零)
:_size(strlen(str))//注意初始化列的初始化顺序(空串_size为0)
, _capacity(_size)
{
//记得先开辟空间再拷贝,因为它现在只是一个指针
_str = new char[_capacity + 1];//加1是给斜杠零的
strcpy(_str, str);//拷贝时把斜杠零也拷贝过去了
}
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
// 传统写法
// s2(s1)
//string(const string& s)
//{
// _str = new char[s._capacity + 1];
// strcpy(_str, s._str);
// _size = s._size;
// _capacity = s._capacity;
//}
//现代写法
void swap(string& s)//注意这是在string类中自己实现的swap
{
std::swap(_str, s._str);//调用的是中的函数swap
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
// s2(s1)
string(const string& s)
: _str(nullptr)//初始化是因为c++没有规定编译器必须对内置类型成员变量初始化,可能是随机值,为了防止调用析构函数时出错
, _size(0)
, _capacity(0)
{
string tmp(s._str);//调用有参构造函数(不能写tmp(s),因为和有参构造函数的参数类型不兼容)
//可以启动调试程序的监视窗口观察这些变量的值
swap(tmp);//这个函数结束之后tmp首先调用析构
//swap(s);
}
// 传统写法
s2 = s3
//string& operator=(const string& s)
//{
// if (this != &s)
// {
// char* tmp = new char[s._capacity + 1];
// strcpy(tmp, s._str);
// delete[] _str;//注意这个变量其实是s2之前的内容,所以需要先delete
// _str = tmp;
// _size = s._size;
// _capacity = s._capacity;
// }
// return *this;
//}
//现代写法
// s2 = s3
string& operator=(string tmp)
{
//这里可以不判断是否自己给自己赋值,因为这个函数是传值调用,执行到swap语句时tmp已经生成了,判不判断都无所谓
//而且一般不会自己给自己赋值
swap(tmp);//本来出了函数之后临时变量tmp也要调用相应的析构函数,s2刚好把不要的(也就是旧的)内容交给tmp去释放
//注意这个过程中s3并不会受影响,还是原来的内容
return *this;
}
char& operator[](size_t pos)
{
assert(pos < _size);//空串时_size为0,也就是说不能访问下标为0的位置的字符
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
size_t capacity() const
{
return _capacity;
}
size_t size() const
{
return _size;
}
const char* c_str() const
{
return _str;
}
void reserve(size_t n)//异地扩容
{
if (n > _capacity)//因为string只需要在空间不够的时候扩容,不需要缩小空间
{
char* tmp = new char[n + 1];//开辟新空间,加1是为了存放斜杠零
strcpy(tmp, _str);//拷贝原来的数据到新的空间
delete[] _str;//释放旧空间
_str = tmp;//把指针指向新空间
_capacity = n;//更新容量
}
}
void resize(size_t n, char ch = '\0')
{
if (n <= _size)
{
_str[n] = '\0';
_size = n;
}
else
{
reserve(n);
while (_size < n)
{
_str[_size] = ch;
++_size;
}
_str[_size] = '\0';
}
}
size_t find(char ch, size_t pos = 0)//从下标为pos的位置开始查找
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;//根据这个函数原型的设定的返回值
}
size_t find(const char* sub, size_t pos = 0)
{
const char* p = strstr(_str + pos, sub);
if (p)
{
return p - _str;
}
else
{
return npos;
}
}
string substr(size_t pos, size_t len = npos)//从下标为pos的位置开始取长度为len的子串
{
string s;
size_t end = pos + len;
if (len == npos || pos + len >= _size) // 有多少取多少
{
len = _size - pos;
end = _size;
}
s.reserve(len);
for (size_t i = pos; i < end; i++)
{
s += _str[i];
}
return s;//返回值s是自定义类型,需要调用拷贝构造函数,然后s的生命周期结束会自动调用析构函数,如果没有显式定义拷贝构造函数,就是浅拷贝
}
void push_back(char ch)
{
if (_size == _capacity)
{
//reserve(_capacity * 2);//这里不能写这个,因为test2中的s2就是空串,_capacity是0,这里扩容之后还是0,所以得用三目表达式
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);//这里是加法,所以不用像上面的函数用三目表达式
}
strcpy(_str + _size, str);
_size += len;
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
// insert(0, 'x')
void insert(size_t pos, char ch)//插入字符
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
size_t end = _size + 1;//要注意类型,无符号是不会小于零的,比较时容易出错,这里加1就是为了防止下面使用end作为下标出错
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
_size++;
}
void insert(size_t pos, const char* str)//插入字符串
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
// 挪动数据
int end = _size;
while (end >= (int)pos)//注意无符号类型不小于零,所以可以类型转换,也可以像上一个函数那样处理
{
_str[end + len] = _str[end];
--end;
}
strncpy(_str + pos, str, len);//不会把str的斜杠零一起拷贝过来
_size += len;
}
void erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else//用后面的字符覆盖想要erase的字符
{
size_t begin = pos + len;
while (begin <= _size)//主要要注意斜杠零
{
_str[begin - len] = _str[begin];
++begin;
}
_size -= len;
}
}
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 *this < s || *this == s;
}
bool operator>(const string& s) const
{
return !(*this <= s);
}
bool operator>=(const string& s) const
{
return !(*this < s);
}
bool operator!=(const string& s) const
{
return !(*this == s);
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
private:
char* _str;//指向字符串的指针
size_t _size;//string实际上的已经使用的空间(也就是不包括斜杠零在内的字符个数)
size_t _capacity;//string的容量(可以理解为string目前能够存放多少个字节的数据)
//const static size_t npos = -1; // 特例,只有const的可以在这里定义
//const static double npos = 1.1; // 报错
public:
const static size_t npos;//为了方便后续实现某些函数的功能而定义
};
const size_t string::npos = -1;
ostream& operator<<(ostream& out, const string& s)//流插入
{
/*for (size_t i = 0; i < s.size(); i++)
{
out << s[i];
}*/
//上面的和下面的都可以
for (auto ch : s)//这里的范围for因为参数s为const所以需要先实现 const_iterator begin() const 和 const_iterator end() const
out << ch;
return out;
}
//istream& operator>>(istream& in, string& s)//流提取
//{
// s.clear();//应该先清空原来的字符串的内容,否则就变成追加了
// char ch;
// //in >> ch;//之所以不用in是因为它取不到空格和换行符,会直接跳过这两个字符,导致下面的循环一直停不下来
// ch = in.get();//用get就可以解决上面的问题
// s.reserve(128);
// while (ch != ' ' && ch != '\n')//cin遇到空格或者回车会停止
// {
// s += ch;
// //in >> ch;
// ch = in.get();
// }
// return in;
//}
//上面的函数不够好,因为使用s.reserve(128)可能会造成资源浪费,不使用s.reserve(128)就得多次扩容,导致效率下降,所以改进一下
istream& operator>>(istream& in, string& s)
{
s.clear();
char buff[129];//这里虽然也是开辟了空间,但是它是一个局部变量,函数结束就释放资源,如果s.reserve(128)可能会使整个string太大
size_t i = 0;
char ch;
ch = in.get();
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 128)
{
buff[i] = '\0';
s += buff;//先把写的保存下来再继续往数组中写入数据
i = 0;
}
ch = in.get();
}
if (i != 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
void test_string1()
{
string s1("hello world");
cout << s1.c_str() << endl;
string s2;
cout << s2.c_str() << endl;
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i] << " ";
}
cout << endl;
string::iterator it = s1.begin();
while (it != s1.end())
{
(*it)++;
cout << *it << " ";
++it;
}
cout << endl;
for (auto& ch : s1)
{
ch++;
cout << ch << " ";
}
cout << endl;
cout << s1.c_str() << endl;
}
void test_string2()
{
string s1("hello world");
cout << s1.c_str() << endl;
s1.push_back(' ');
s1.append("hello GGYH hello GGYH");
cout << s1.c_str() << endl;
s1 += '#';
s1 += "*********************";
cout << s1.c_str() << endl;
string s2;
s2 += '#';
s2 += "*********************";
cout << s2.c_str() << endl;
}
void test_string3()
{
string s1("hello world");
cout << s1.c_str() << endl;
s1.insert(5, '%');//在下标为5的位置插入%,原来下标为5以及之后的数据往后挪
cout << s1.c_str() << endl;
s1.insert(s1.size(), '%');
cout << s1.c_str() << endl;
s1.insert(0, '%');
cout << s1.c_str() << endl;
}
void test_string4()
{
string s1("hello world");
string s2("hello world");
cout << (s1 >= s2) << endl;
s1[0] = 'z';
cout << (s1 >= s2) << endl;
cout << s1 << endl;
cin >> s1;
cout << s1 << endl;
/*char ch1, ch2;//通过这里的测试,可以发现其实换行和空格都只会被跳过,并不会被读取
cin >> ch1 >> ch2;*/
}
void test_string5()
{
string s1("hello world");
s1.insert(5, "abc");
cout << s1 << endl;
s1.insert(0, "xxx");
cout << s1 << endl;
s1.erase(0, 3);
cout << s1 << endl;
s1.erase(5, 100);
cout << s1 << endl;
s1.erase(2);
cout << s1 << endl;
}
void test_string6()
{
string s1("hello world");
cout << s1 << endl;
s1.resize(5);
cout << s1 << endl;
s1.resize(25, 'x');
cout << s1 << endl;
}
void test_string7()
{
string s1("test.cpp.tar.zip");
//size_t i = s1.find('.');
//string s2 = s1.substr(i);
//cout << s2 << endl;
string s3("https://legacy.cplusplus.com/reference/string/string/rfind/");
//string s3("ftp://www.baidu.com/?tn=65081411_1_oem_dg");
string sub1, sub2, sub3;
size_t i1 = s3.find(':');
if (i1 != string::npos)
sub1 = s3.substr(0, i1);
else
cout << "没有找到i1" << endl;
size_t i2 = s3.find('/', i1 + 3);//加3是为了跳过:之后的//
if (i2 != string::npos)
sub2 = s3.substr(i1 + 3, i2 - (i1 + 3));
else
cout << "没有找到i2" << endl;
sub3 = s3.substr(i2 + 1);//剩下的全部取出来
cout << sub1 << endl;
cout << sub2 << endl;
cout << sub3 << endl;
}
void test_string8()
{
string s1("hello world");//调用有参构造函数
string s2 = s1;//调用拷贝构造
cout << s1 << endl;
cout << s2 << endl;
string s3("xxxxxxxxxxxxxxxxxxx");
s2 = s3;//先拷贝构造,再调用运算符重载
cout << s2 << endl;
cout << s3 << endl;
}
void test_string9()
{
string s1("hello world");
cin >> s1;
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
}
}
Test.cpp代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
#include
using namespace std;
#include"string.h"
int main()
{
GGYH::test_string7();
return 0;
}
string还有部分功能需要我们后面学习容器适配器等知识才能实现 ~