1.源代码:
string.h
#pragma once
#include<assert.h>
#include<iostream>
#include<cassert>
using namespace std;
namespace fjw
{
class string
{
public:
typedef char* iterator;//不同容器中迭代器的类型是不一样的,typedef之后有利于做统一处理
//对类型也做了封装,上层使用的是iterator,隐藏了实现细节
typedef const char* const_iterator;
iterator begin();
iterator end();
const_iterator begin()const;
const_iterator end()const;
//构造
string(const char* s = "\0");//缺省值在.h文件中方法声明时给出
//拷贝
string(string& str);
~string();
const char* c_str()const;
size_t size()const;
//因为对于const对象你只能读串的内容而不能修改串的内容
//对于非const对象你能读串的内容也能修改串的内容
//所以方法返回值的类型是不一样的,所以要重载成两个函数
const char& operator[](size_t pos)const;
char& operator[](size_t pos);
void reserve(size_t n);
void push_back(char ch);
void append(const char* s);
string& operator +=(char ch);
string& operator +=(const char* s);
string& operator +=(const string& str);
void insert(size_t pos, char ch);
void insert(size_t pos, const char* s);
void erase(size_t pos, size_t len = npos);//缺省值只能是常量、全局变量
size_t find(char ch, size_t pos);
size_t find(const char* s, size_t pos);
//string& operator =(string& str);
string& operator =(string tmp);
void swap(string& str);
string substr(size_t pos = 0,size_t len = npos);
bool operator >(const string& str)const;
bool operator<(const string& str)const;
bool operator==(const string& str)const;
bool operator>=(const string& str)const;
bool operator<=(const string& str)const;
bool operator!=(const string& str)const;
void clear();
private:
//成员变量
char* _str;
size_t _size;
size_t _capacity;
//static修饰的成员变量会变成全局变量,定义必须在.cpp文件中,不能在.h文件中
const static size_t npos;
//特例:整型支持,其他类型都不支持
//const static size_t npos = -1;
//不支持
//const static double N = 2.2;
};
//之前实现日期类中重载流插入和流提取时声明成友元函数是因为要访问类中的私有,
//这里我们可以不用访问私有,所以不用声明成友元函数
ostream& operator << (ostream& cout, const string& str);//流插入
istream& operator >> (istream& cout, string& str);//流提取
}
string.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
namespace fjw
{
const size_t string::npos = -1;//全局变量必须在.cpp文件中定义,
ostream& operator << (ostream& cout, const string& str)//流插入
{
cout << str.c_str() << endl;
return cout;
}
void string::clear()
{
_str[0] = '\0';
_size = 0;
}
istream& operator >> (istream& cin, string& str)//流提取
{
/*char ch ;
scanf("%c",&ch);
while (ch != '\n' && ch != ' ')
{
str += ch;
scanf("%c",&ch);
}
return cin;*/
//如果给已有字符的串中赋值,要将原串的内容覆盖,所以这里先清理原串内容
/*str.clear();
char ch = cin.get();
while (ch != '\n' && ch != ' ')
{
str += ch;
ch = cin.get();
}
return cin;*/
//如果字符串特别长的话,需要多次调用尾插,频繁的扩容,会有不小的消耗
//通过缓冲区来解决该问题
str.clear();
char* buffer = new char[120];
char ch = cin.get();
int i = 0;
while(ch != ' ' && ch != '\n')
{
buffer[i++] = ch;
if (i==119)//先往buff里面放,缓冲区满了之后往串里面放,
{
buffer[i] = '\0';
str += buffer;
i = 0;
}
ch = cin.get();
}
if (i!=0)//把buff剩余的放到串里面
{
buffer[i] = '\0';
str += buffer;
}
delete[] buffer;
return cin;
}
//构造
string::string(const char* s)//或者给" ",但不能给'\0'
:_size(strlen(s))
{
_str=new char[_size+1];
_capacity=_size;
strcpy(_str, s);
}
//拷贝
//string::string(string& str)
//{
// _size = str._size;
// _capacity = str._capacity;
// _str = new char[_capacity+1];//注意:开辟_capacity+1个空间
// strcpy(_str, str._str);
//}
string::string(string& str)
{
string tmp(str._str);
swap(tmp);
}
string::~string()
{
delete[] _str;
_str = nullptr;
_capacity = 0;
_size = 0;
}
const char* string::c_str()const
{
return _str;
}
size_t string::size()const
{
return _size;
}
const char& string::operator[](size_t pos)const
{
assert(pos < _size);//断言
return _str[pos];
}
char& string::operator[](size_t pos)//为了对串的内容进行修改,所以返回引用
{
assert(pos < _size);//断言
return _str[pos];
}
string::iterator string::begin()
{
return _str;
}
string::iterator string::end()
{
return _str + _size;
}
string::const_iterator string::begin()const
{
return _str;
}
string::const_iterator string::end()const
{
return _str + _size;
}
void string::reserve(size_t n)//C++没有像C语言一样有realloc(),需要手动扩容
{ //手动扩容的原因是C++不仅要支持对自定义类型开辟空间而且要支持对自定义类型开辟空间
//手动扩容 //自定义类型大小是不同的所以只能手动扩
char* tmp = new char[n+1];
strcpy(tmp , _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
void string::push_back(char ch)
{
//if (_size == _capacity)
//{
// size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
// reserve(newcapacity);
//}
//_str[_size] = ch; //_size指向的是'\0',还是串的大小(不包含'\0')
//_str[_size + 1] = '\0';
//++_size;
insert(_size, ch);
}
void string::append(const char* s)
{
/*size_t len = strlen(s);
if ((len + _size) > _capacity)
{
reserve(len + _size);
}
strcpy(_str+_size, s);
_size += len;*/
insert(_size, s);
}
string& string::operator +=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator +=(const char* s)
{
append(s);
return *this;
}
string& string::operator +=(const string& str)
{
append(str._str);
return *this;
}
//指定位置插入
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(newcapacity);
}
int end = _size;
while (end >= (int)pos)
{
_str[end + 1] = _str[end];
--end;
}
/*size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end-1];
--end;
}*/
_str[pos] = ch;
++_size;
}
void string::insert(size_t pos, const char* s)
{
assert(pos <= _size);
size_t len = strlen(s);
if ((len + _size) > _capacity)
{
reserve(len + _size);
}
int end = _size;
while (end>=(int)pos)
{
_str[end + len] = _str[end];
--end;
}
memcpy(_str + pos, s, len);//不能用strcpy(),会将'\0'也拷贝过去
_size += len;
}
void string::erase(size_t pos, size_t len)
{
assert(pos < _size);
if ((pos + len)>=_size)
{
_str[pos] = '\0';
_size = pos;
}
else {
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
size_t string::find(char ch, size_t pos)
{
for (int i=pos;i<_size;++i)
{
if (_str[i] == ch)
{
return i;
}
return npos;
}
}
size_t string::find(const char* s, size_t pos)
{
const char* p=strstr(_str+pos,s);
return p - _str;
}
//string& string::operator =(string& str) {
// if (this!=&str)
// {
// //char* tmp = new char[_capacity + 1];//注意:开辟_capacity+1个空间
// //strcpy(tmp, str._str);
// //delete[] _str;
// //_str = tmp;
// //_size = str._size;
// //_capacity = str._capacity;
// string tmp(str._str);
// swap(tmp);
// }
// return *this;
//}
string& string::operator =(string tmp)
{
swap(tmp);
return *this;
}
void string::swap(string& str)
{
std::swap(_str,str._str);
std::swap(_size,str._size);
std::swap(_capacity,str._capacity);
}
string string::substr(size_t pos , size_t len)
{
if ((pos+len) > _size)
{
string substr(_str+pos);
return substr;
}
string substr;
substr.reserve(len);
for (int i = 0;i<len;++i)
{
substr += _str[pos + i];
}
return substr;
}
bool string::operator > (const string& str)const
{
return strcmp(_str, str._str) > 0;
}
bool string::operator < (const string& str)const
{
return !( *this >= str );
}
bool string::operator == (const string& str)const
{
return strcmp(_str, str._str) == 0;
}
bool string::operator >= (const string& str)const
{
return *this > str || *this == str;
}
bool string::operator <= (const string& str)const
{
return !( *this > str );
}
bool string::operator != (const string& str)const
{
return !( *this == str );
}
}
test.cpp
#include"string.h"
void test_string1()
{
fjw::string s("hello world");
cout << s.c_str() << endl;
for (int i = 0; i < s.size(); i++)
{
s[i] = 'x';
}
for (int i = 0; i < s.size(); i++)
{
cout << s[i] <<' ';
}
cout << endl;
}
//不同文件、同一文件同名的命名空间,是同一个空间,所以下面的不用像上面的一样指定fjw空间
namespace fjw
{
void test_string2()
{
string s("hello world");
string::iterator it1 = s.begin(); // 上层使用的是iterator,隐藏了实现细节,进一步做了封装
while (it1<s.end())
{
cout << *it1 << " ";
++it1;
}
cout << endl;
for (auto e : s) //范围for前提是要实现好begin()、end(),底层就是迭代器
{
cout << e << ' ';
}
cout << endl;
}
}
void test_string3()
{
fjw::string s("hello world");
s.insert(2,'x');
cout << s.c_str() << endl;
s.insert(0, 'x');
cout << s.c_str() << endl;
s.insert(0, "yyy");
cout << s.c_str() << endl;
s.erase(1, 2);
cout << s.c_str() << endl;
fjw::string s1(s);
}
void test_string4()
{
fjw::string s;
cin >> s;
cout << s << endl;
string s1("hello world");
string s2(s1);
cout << s2 << endl;
}
int main()
{
test_string1();
fjw::test_string2();
test_string3();
test_string4();
return 0;
}
2.测试结果:
3.分析
3.1构造函数
int main()
{
fjw::string s;
cout<< s <<endl;
return 0;
}
按照常理来说对于空字符串的打印是什么都不打印,但不会报错,所以以上的默认构造的实现是不可取的,那又为什么会报错,打印是找到0地址空间,打印字符串直到’\0’停止,但又不知到什么才遇到’\0’,所以编译会报错。C++中的nullptr作为关键字引入的,所以释放是不会报错的,C语言中的NULL是宏定义,释放会报错。
【解决方案】
这两个可以合并,写成一个全缺省
3.2指定位置插入insert()(指定位置的插入)
调试结果:
通过调试报出:访问冲突,由于end是size_t类型的end=0时再–会是一个很大的正整数(无符号整形的最大值),这时就越界访问了,怎么改呢?有人想到把end改成int类型的,先验证一下是否可以
还是一样的结果,原因:如果操作符两边的操作数类型不一样,会发生隐式类型转换,这里将有符号转换成无符号。
【解决方案】
1、将pos强转成int类型
2、end指向移向的目标空间,end减到0就不会进去
3.3swap()
1.库中的算法swap()函数:
算法中的swap()函数通过新建一个临时变量,通过临时变量来交换两个值,
对于串来说交换两个类要进行多次的深拷贝和释放,这样效率会很低,所以串中自己实现了一个swap()。
2.string类中的成员函数:
内部实现原理:交换两个指针的指向
3.string类中的全局函数:
内部实现原理:调用的是sting类中的swap()成员函数
3.4重载流插入(<<) 和 流提取(>>)
之前实现日期类中重载流插入和流提取时声明成友元函数是因为要访问类中的私有,这里我们可以不用访问私有,所以不用声明成友元函数。
这样写流提取运算符重载函数是不行的,提取不到空字符(’ ‘),
那scanf()能否提取到空字符(’ ‘),如果scanf()能提取到那能否用scanf()来实现这里的流提取运算符重载函数呢?
scanf会提取到空格字符(’ ‘)和换行字符(’\n’),(同时也会停止下一个的字符的提取,因为空格和换行默认是字符之间的分割)我们用scanf()来实现流提取运算符函数的重载
会发现这样写是可以的,但尽量不要这样写,因为C++的流与C语言的流是不一样的,缓冲区不一样,如果scanf()和cin>>混用的话会导致出错的。
【解决方案】
使用流提取类里面的get()方法
由于要对原有串的内容进行覆盖,所以要先清理原有串的内容,再进行放入新的内容,所以要实现一个clear()方法
如果字符串特别长的话,需要多次调用尾插,频繁的扩容,会有不小的消耗,通过缓冲区的思路来做一个优化
3.5拷贝函数
这里的拷贝构造函数是通过开辟一个空间,将内容拷贝到这个空间中,然后指向该空间,如果树也这样实现,就要递归拷贝,这样实现构造函数起来就变得复杂了。
我们可以通过调用构造函数来创建一个该内容的串(树),然后交换两个串(树),这样就达到拷贝的效果,其实代码执行效率是一样的,都要开空间,然后在拷贝,但是我们可以调用构造函数,就不用再写开空间、拷贝步骤的代码了,实现构造函数就变得简单了,实现效率提高了。
赋值符号重载函数也是如此
简写代码:
再简写代码:
这里一定是转值,而不是传引用。
如果传引用的话,就是s1和s2的交换,s2变成空串,s1变成"hello world",就不是赋值了。