上篇博客主要讲了std标准库中string容器的基本使用方法。为了更加透彻的理解string,同时也是增强基础代码能力,所以来对string进行一个基本的实现。
一.String的基础实现
这里先放写好的string.h,string.cpp和test.cpp代码。
cpp
#pragma once
#include<iostream>
#include<string>
#include<assert.h>
#include<cstring>
using namespace std;
namespace Practice
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
string();
string(const char* str);
string(size_t n , char c);
string(const string& s);
string(const string& s, size_t pos, size_t len = npos);
string& operator=(const string& s);
~string();
size_t size() const;
size_t capacity() const;
char* c_str() const;
char& operator[](size_t i);
const char& operator[](size_t i) const;
iterator begin();
const_iterator begin()const;
iterator end();
const_iterator end()const;
void reserve(size_t target_capacity);
void push_back(const char c);
void pop_back();
void append(const char* str);
string& operator+=(const char* str);
string& operator+=(const char c);
size_t find(const char* s, size_t pos = 0)const;
size_t find(const char c, size_t pos = 0)const;
string& insert(size_t pos,const char* str);
string& insert(size_t pos,const char c);
string& erase(size_t pos = 0, size_t len = npos);
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;
void clear();
private:
char* _str;
size_t _size = 0;
size_t _capacity = 15;
public:
static size_t npos;
};
ostream& operator << (ostream& out, const string& s);
istream& operator>>(istream& in, string& s);
istream& getline(istream& in, string& s,char delim = '\n');
}
string.h文件中,主要包含了对所实现的各个函数的声明。
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
namespace Practice
{
size_t string::npos = -1;
string::string()
:_str(new char[1])
, _size(0)
, _capacity(15)
{
_str[0] = '\0';
}
string::string(const char* str)
:_size(strlen(str))
{
if (_size > 15)
{
_capacity = _size;
}
else
{
_capacity = 15;
}
_str = new char[_capacity+1];
strcpy(_str, str);
}
string::string(size_t n, char c)
:_size(n)
{
if (n > 15)
{
_capacity = n;
}
else
{
_capacity = 15;
}
_str = new char[_capacity + 1];
for (size_t i = 0; i < n; i++)
{
_str[i] = c;
}
_str[_size] = '\0';
}
string::string(const string& s)
{
_str = new char[s._capacity + 1];
memcpy(_str, s._str, s._size+1);
_size = s._size;
_capacity = s._capacity;
}
string::string(const string& s, size_t pos, size_t len )
{
assert(pos >= 0 && pos < s._size);
if (len == npos || len > (s._size - pos))
{
_capacity = s._size - pos;
}
else
{
_capacity = len;
}
_str = new char[_capacity + 1];
memcpy(_str, s._str + pos, _capacity);
_size = _capacity;
_str[_size] = '\0';
}
string& string::operator=(const string& s)
{
char* tmp = new char[s._capacity + 1];
memcpy(tmp, s._str, s._size+1);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
return *this;
}
string::~string()
{
delete[] _str;
_size = _capacity = 0;
}
size_t string::size() const
{
return _size;
}
size_t string::capacity() const
{
return _capacity;
}
char* string::c_str() const
{
return _str;
}
char& string::operator[](size_t i)
{
assert(i >= 0 && i < _size);
return _str[i];
}
const char& string::operator[](size_t i) const
{
assert(i >= 0 && i < _size);
return _str[i];
}
string::iterator string::begin()
{
return _str;
}
string::const_iterator string::begin() const
{
return _str;
}
string::iterator string::end()
{
return _str + _size;
}
string::const_iterator string::end()const
{
return _str + _size;
}
void string::reserve(size_t target_capacity)
{
if (target_capacity > _capacity)
{
_capacity = target_capacity;
char* tmp = new char[_capacity+1];
memcpy(tmp, _str, _size+1);
delete[] _str;
_str = tmp;
return;
}
else
{
return;
}
}
void string::push_back(const char c)
{
if (_size == _capacity)
{
size_t newcapacity =(_capacity == 0 ? 15:_capacity * 2);
reserve(newcapacity);
}
_str[_size++] = c;
_str[_size] = '\0';
}
void string::pop_back()
{
assert(_size != 0);
_size--;
_str[_size] = '\0';
}
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size+len);
}
strcpy(_str + _size, str);
_size += len;
}
string& string::operator+=(const char* str)
{
(*this).append(str);
return *this;
}
string& string::operator+=(const char c)
{
(*this).push_back(c);
return *this;
}
string& string::insert(size_t pos,const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = _size + len;
while (end >= pos+ len)
{
_str[end] = _str[end - len];
end--;
}
memcpy(_str + pos, str, len);
_size += len;
_str[_size] = '\0';
return *this;
}
string& string::insert(size_t pos ,const char c)
{
assert(pos <= _size);
if (_size == _capacity)
{
size_t newcapacity = (_capacity == 0 ? 15 : _capacity * 2);
reserve(newcapacity);
}
size_t end = _size+1;
while (end > pos)
{
_str[end] = _str[end-1];
end--;
}
_str[pos] = c;
_str[++_size] = '\0';
return *this;
}
string& string::erase(size_t pos , size_t len)
{
assert(pos >= 0 && pos < _size);
if (len == npos || len > (_size - pos))
{
_size = pos;
_str[pos] = '\0';
}
else
{
while (pos+len <= _size)
{
_str[pos] = _str[pos + len];
pos++;
}
_size = _size - len;
_str[_size] = '\0';
}
return *this;
}
size_t string::find(const char* s, size_t pos)const
{
assert(pos < _size);
char* str = strstr(_str, s);
if (str == nullptr)
{
return npos;
}
else
{
return str - _str;
}
}
size_t string::find(const char c, size_t pos)const
{
assert(pos < _size);
for (size_t i = 0; i < _size; i++)
{
if (_str[i] == c)
{
return i;
}
}
return npos;
}
bool string::operator==(const string& s)const
{
size_t l1 = 0;
size_t l2 = 0;
while (l1 < _size && l2 < s.size())
{
if (_str[l1] < s[l2])
{
return false;
}
else if (_str[l1] > s[l2])
{
return false;
}
else
{
l1++;
l2++;
}
}
return ((l1 == _size) && (l2 == s.size()));
}
bool string::operator!=(const string& s)const
{
return !((*this) == s);
}
bool string::operator<(const string& s)const
{
size_t l1 = 0;
size_t l2 = 0;
while (l1 < _size && l2 < s.size())
{
if (_str[l1] < s[l2])
{
return true;
}
else if (_str[l1] > s[l2])
{
return false;
}
else
{
l1++;
l2++;
}
}
return (l2 < s.size());
}
bool string::operator<=(const string& s)const
{
return (*this) < s || (*this) == s;
}
bool string::operator>(const string& s)const
{
return !((*this) <= s);
}
bool string::operator>=(const string& s)const
{
return (*this) > s || (*this) == s;
}
ostream& operator << (ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); i++)
{
cout << s[i];
}
return out;
}
void string::clear()
{
_str[0] = '\0';
_size = 0;
}
//以下的两种写法会涉及到开辟空间多次的问题
//istream& operator>>(istream& in, string& s)
//{
// s.clear();
// char ch = in.get();
// while (ch != ' ' && ch != '\n')
// {
// s += ch;
// ch = in.get();
// }
// return in;
//}
istream& operator>>(istream& in, string& s)
{
s.clear();
char tmp[256];
size_t i = 0;
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
tmp[i++] = ch;
if (i == 255)
{
tmp[i] = '/0';
s += tmp;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
tmp[i] = '\0';
s += tmp;
}
return in;
}
//istream& getline(istream& in, string& s, char delim)
//{
// s.clear();
// char ch = in.get();
// while (ch != delim)
// {
// s += ch;
// ch = in.get();
// }
// return in;
//}
istream& getline(istream& in, string& s, char delim)
{
s.clear();
char tmp[256];
size_t i = 0;
char ch = in.get();
while (ch != delim)
{
tmp[i++] = ch;
if (i == 255)
{
tmp[i] = '/0';
s += tmp;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
tmp[i] = '\0';
s += tmp;
}
return in;
}
}
string.cpp主要是对于各个函数的实现过程。
test.cpp主要是对于实现函数的测试过程。
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
namespace Practice
{
void test1()
{
//构造函数的测试
string s1;
string s2("hello world");
string s3(20, 'c');
string s4 = s2;
string s5(s3, 0, 5);
}
void test2()
{
string s1("Hello World");
for (size_t i = 0; i < s1.size(); i++)
{
s1[i]++;
cout << s1[i];
}
cout << endl;
string s2("Hello World");
string::iterator it = s2.begin();
while (it != s2.end())
{
cout << ++(*it);
it ++;
}
cout << endl;
const string s3("Hello World");
string::const_iterator cit = s3.begin();
while (cit != s3.end())
{
//cout << ++(*cit);//表达式必须是可修改的左值
cout << *it;
cit++;
}
}
void test3()
{
string s1;
cout << "size:" << s1.size() << endl;
cout << "capacity:" << s1.capacity() << endl;
cout << endl;
s1.reserve(50);
cout << "size:" << s1.size() << endl;
cout << "capacity:" << s1.capacity() << endl;
cout << endl;
s1.reserve(5);//没有缩容
cout << "size:" << s1.size() << endl;
cout << "capacity:" << s1.capacity() << endl;
cout << endl;
s1.push_back('a');
s1.push_back('b');
s1.push_back('c');
s1.push_back('d');
s1.push_back('e');
s1.push_back('f');
cout << s1.c_str() << endl;
cout << "size:" << s1.size() << endl;
cout << "capacity:" << s1.capacity() << endl;
cout << endl;
s1.append("Hello World");
cout << s1.c_str() << endl;
cout << "size:" << s1.size() << endl;
cout << "capacity:" << s1.capacity() << endl;
cout << endl;
s1 += 'x';
s1 += "cccccccc";
cout << s1.c_str() << endl;
cout << "size:" << s1.size() << endl;
cout << "capacity:" << s1.capacity() << endl;
cout << endl;
s1.pop_back();
s1.pop_back();
s1.pop_back();
s1.pop_back();
s1.pop_back();
cout << s1.c_str() << endl;
cout << "size:" << s1.size() << endl;
cout << "capacity:" << s1.capacity() << endl;
cout << endl;
}
void test4()
{
string s1("xxxxxxxxxx");
s1.insert(2, "abcdef");
cout << s1.c_str() << endl;
s1.insert(0, 'y');
cout << s1.c_str() << endl;
s1.insert(0, "123456");
cout << s1 << endl;
}
void test5()
{
string s1("abcdefg");
s1.erase(0, 2);
cout << s1 << endl;
s1.erase();
cout << s1 << endl;
}
void test6()
{
string s1;
string s2;
cin >> s1>> s2;
cout << s1 << endl;
cout << s2 << endl;
//string s3;
//getline(cin, s3, '!');
//cout << s3 << endl;
}
}
int main()
{
//Practice::test1();
//Practice::test2();
//Practice::test3();
//Practice::test4();
//Practice::test5();
Practice::test6();
return 0;
}
二.各个函数的讲解
cpp
namespace Practice
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
string();
string(const char* str);
string(size_t n , char c);
string(const string& s);
string(const string& s, size_t pos, size_t len = npos);
string& operator=(const string& s);
~string();
size_t size() const;
size_t capacity() const;
char* c_str() const;
char& operator[](size_t i);
const char& operator[](size_t i) const;
iterator begin();
const_iterator begin()const;
iterator end();
const_iterator end()const;
void reserve(size_t target_capacity);
void push_back(const char c);
void pop_back();
void append(const char* str);
string& operator+=(const char* str);
string& operator+=(const char c);
size_t find(const char* s, size_t pos = 0)const;
size_t find(const char c, size_t pos = 0)const;
string& insert(size_t pos,const char* str);
string& insert(size_t pos,const char c);
string& erase(size_t pos = 0, size_t len = npos);
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;
void clear();
private:
char* _str;
size_t _size = 0;
size_t _capacity = 15;
public:
static size_t npos;
};
ostream& operator << (ostream& out, const string& s);
istream& operator>>(istream& in, string& s);
istream& getline(istream& in, string& s,char delim = '\n');
}
首先创建了一个命名空间,为了和std标准库中的string区分,所以用命名空间来区分。
2.1私有成员变量和静态成员函数npos
cpp
private:
char* _str;
size_t _size = 0;
size_t _capacity = 15;
public:
static size_t npos;
string的底层实现,类似于C语言基础数据结构中的顺序表,可以将其理解为一个存放字符串的顺序表。包含一个指向字符串数组的char*指针_str,一个指向最后一个元素的下标_size,一个指示当前数组容量的_capacity。
在string中还存在一个size_t 类型的静态成员变量npos,为-1,用来取到字符串的最后一个元素位置。
2.2默认成员函数
cpp
string();
string(const char* str);
string(size_t n , char c);
string(const string& s);
string(const string& s, size_t pos, size_t len = npos);
string& operator=(const string& s);
~string();
其次是默认成员函数的相关实现,包括构造函数、拷贝构造函数、赋值运算符重载和析构函数。
2.2.1构造函数
构造函数主要重载了三种类型:1.空字符串;2.常量字符串构造;3.n个字符c构造
cpp
string::string()
:_str(new char[1])
, _size(0)
, _capacity(15)
{
_str[0] = '\0';
}
string::string(const char* str)
:_size(strlen(str))
{
if (_size > 15)
{
_capacity = _size;
}
else
{
_capacity = 15;
}
_str = new char[_capacity+1];
strcpy(_str, str);
}
string::string(size_t n, char c)
:_size(n)
{
if (n > 15)
{
_capacity = n;
}
else
{
_capacity = 15;
}
_str = new char[_capacity + 1];
for (size_t i = 0; i < n; i++)
{
_str[i] = c;
}
_str[_size] = '\0';
}
首先是空字符串构造,只需要开辟一个空间来存放 '\0'即可。在string中所实现的_size和_capacity均不统计'\0'的位置。所以之后的函数的实现过程中也要注意在_size位置存放的是'\0',数据增删查改的过程中要时刻注意这个'\0'。
第二是常量字符串的构造,首先利用strlen函数统计出该常量字符串有多少字符,之后利用进行空间开辟。因为要存放'\0'所以需要多开辟一个空间。之后利用strcpy函数进行数据拷贝即可。
第三是n个字符c的构造,首先根据传递的形参n,来构造一个能存放下n个字符的数组空间,之后利用for循环n次进行数据的存放即可。注意最后一个位置的'\0'。
2.2.2拷贝构造
cpp
string::string(const string& s)
{
_str = new char[s._capacity + 1];
memcpy(_str, s._str, s._size+1);
_size = s._size;
_capacity = s._capacity;
}
string::string(const string& s, size_t pos, size_t len )
{
assert(pos >= 0 && pos < s._size);
if (len == npos || len > (s._size - pos))
{
_capacity = s._size - pos;
}
else
{
_capacity = len;
}
_str = new char[_capacity + 1];
memcpy(_str, s._str + pos, _capacity);
_size = _capacity;
_str[_size] = '\0';
}
拷贝构造实现了两个重载方式,第一种是单纯利用一个string对象的拷贝;第二种实现了利用string对象的某一部分进行拷贝。
第一种,由于明确了解需要拷贝的对象中有多少内容、多少空间大小,所以只需要依次按照该对象的各项参数进行拷贝即可。memcpy拷贝数据的过程中,要注意多拷贝一个数据,所以是_size+1来拷贝,这样会将末尾的'\0'进行拷贝;或者拷贝_size个字符,然后手动将_size位置的字符置为'\0'。
第二种,给定了要拷贝字符串的长度。如果该长度是缺省值npos或者给定的要拷贝对象的长度大于该字符串末尾到pos的距离,就说明该字符串需要从当前位置拷贝到字符串末尾。这样的需要对当前对象开辟一个由拷贝对象字符串末尾到pos位置距离长度的数组。如果len小于这个长度,只需要开辟len个字符数组的空间即可。之后的拷贝过程类似。注意在_size位置设置'\0'。注意这个拷贝构造不能通过移动_size+1个字符来确保移动了'\0',因为挪动的数据不一定达到末尾。
2.2.3赋值运算符重载
cpp
string& string::operator=(const string& s)
{
char* tmp = new char[s._capacity + 1];
memcpy(tmp, s._str, s._size+1);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
return *this;
赋值运算符重载的实现方式和构造函数较为类似。唯一的不同是,原本的对象中有无资源需要删除。创建一个临时对象,空间大小取决于要赋值的对象。之后继续进行数据的挪动即可。最后将原来的数据空间释放,并指向新开辟的数据空间即可。
2.2.4析构函数
cpp
string::~string()
{
delete[] _str;
_size = _capacity = 0;
}
利用delete\[\]对类中的数组空间进行释放,之后将_size和_capacity置零(可有可无)即可。
2.3 C语言接口函数和取数值相关函数
cpp
size_t string::size() const
{
return _size;
}
size_t string::capacity() const
{
return _capacity;
}
char* string::c_str() const
{
return _str;
}
三个函数,分别返回_size,_capacity和_str,为外部提供访问接口。均设置为const成员函数,来为const string对象提供访问机会。
2.4 遍历相关函数
遍历相关函数,包括方括号重载,迭代器的实现:
cpp
typedef char* iterator;
typedef const char* const_iterator;
char& string::operator[](size_t i)
{
assert(i >= 0 && i < _size);
return _str[i];
}
const char& string::operator[](size_t i) const
{
assert(i >= 0 && i < _size);
return _str[i];
}
string::iterator string::begin()
{
return _str;
}
string::const_iterator string::begin() const
{
return _str;
}
string::iterator string::end()
{
return _str + _size;
}
string::const_iterator string::end()const
{
return _str + _size;
}
首先是方括号重载,实现两个版本,分别对string对象和const string对象提供\[\]下标访问的接口。string对象返回char&,用来可以对内容进行修改;const string对象不能进行修改。
其次是iterator迭代器,目前可以用较为简单的方式来实现,直接用typedef char* iterator即可。同时const_iterator,用const char* 来实现即可。
begin函数和end函数都重载了const_iterator版本。begin返回数组的首地址即可,end返回指向数组的最后一个元素的位置,即_str+_size。
2.5 reserve开辟空间函数
cpp
void string::reserve(size_t target_capacity)
{
if (target_capacity > _capacity)
{
_capacity = target_capacity;
char* tmp = new char[_capacity+1];
memcpy(tmp, _str, _size+1);
delete[] _str;
_str = tmp;
return;
}
else
{
return;
}
std库中的reserve开辟空间,只可以将容量增大,不可缩小。
传参为目标空间大小。如果该参数大于当前对象的空间,就进行空间增大,否则不改变当前空间大小。
改变空间大小,开辟一个新的数组tmp,之后利用memcpy将原数组中的元素拷贝到新数组中。释放原数组的空间,并将_str赋值为tmp即可。
在这里注意开辟空间时仍要多开辟一个用来存放'\0',同时注意删除原数组中的元素,防止内存泄漏。
2.6 增加删除相关函数
2.6.1 push_back()和pop_back()
cpp
void string::push_back(const char c)
{
if (_size == _capacity)
{
size_t newcapacity =(_capacity == 0 ? 15:_capacity * 2);
reserve(newcapacity);
}
_str[_size++] = c;
_str[_size] = '\0';
}
void string::pop_back()
{
assert(_size != 0);
_size--;
_str[_size] = '\0';
}
push_back函数用来向字符串末尾位置添加一个字符,首先判断容量大小是否足够,如果不够,采用二倍扩容机制。之后在字符串的_size位置添加一个字符后,_size自增,之后在结束位置添加一个'\0'即可。
pop_back函数用来删除字符串结尾的一个元素。要注意当前字符串不能仅有'\0',如果只有'\0',表示当前字符串为空,不能进行删除。其余情况,仅需将_size向前挪动一个字符,然后将当前位置赋值为'\0'即可。
2.6.2 append()
cpp
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size+len);
}
strcpy(_str + _size, str);
_size += len;
}
append函数用来向字符串末尾添加一串字符。首先需要判断容量问题,如果当前位置的坐标加上所需添加的字符串长度大于当前容量,那么需要扩容。之后将字符串依次拷贝到当前对象的字符串末尾即可。最后改变_size的指向。
2.6.3 operator+=
cpp
string& string::operator+=(const char* str)
{
(*this).append(str);
return *this;
}
string& string::operator+=(const char c)
{
(*this).push_back(c);
return *this;
}
operator+=的重载函数,重载了两个版本。分别是在当前对象末尾添加一串字符和一个字符。分别调用push_back和append即可。
2.6.4 insert和erase
cpp
string& string::insert(size_t pos,const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = _size + len;
while (end >= pos+ len)
{
_str[end] = _str[end - len];
end--;
}
memcpy(_str + pos, str, len);
_size += len;
_str[_size] = '\0';
return *this;
}
string& string::insert(size_t pos ,const char c)
{
assert(pos <= _size);
if (_size == _capacity)
{
size_t newcapacity = (_capacity == 0 ? 15 : _capacity * 2);
reserve(newcapacity);
}
size_t end = _size+1;
while (end > pos)
{
_str[end] = _str[end-1];
end--;
}
_str[pos] = c;
_str[++_size] = '\0';
return *this;
}
string& string::erase(size_t pos , size_t len)
{
assert(pos >= 0 && pos < _size);
if (len == npos || len > (_size - pos))
{
_size = pos;
_str[pos] = '\0';
}
else
{
while (pos+len <= _size)
{
_str[pos] = _str[pos + len];
pos++;
}
_size = _size - len;
_str[_size] = '\0';
}
return *this;
}
insert函数重载了两个版本,分别是在某一个下标的位置插入一个字符;在某个下标的位置插入一串字符。
首先讲解在某个下表的位置插入一个字符,对应上面代码的第二个函数。首先检查空间大小,如果不够就进行扩容。定义变量end,赋值为_size+1,表示当前字符串末尾的下一个位置。开始循环赋值,将end的位置赋值为end-1位置的值。循环结束条件为end>pos,即end-1 =pos已经赋值给过end的位置了,pos位置的原数据已经向后进行挪动。之后将pos位置的数据赋值为要插入的字符c即可,最后改变_size的大小即可。
之后是在某一下标位置插入一串字符。仍然先进行判断容量大小开辟空间。于上面的思路类似,只不过上面的函数只需要向后依次挪动1个字符,这个函数需要依次向后挪动要插入的字符串长度的字符。所以定义变量end = _size+len。将end位置赋值为end-len的值。循环的结束条件为:end - len >= pos,但是要十分注意这样的写法是错误的,因为end的类型为size_t,可能会出现end-pos = -1,但size_t的-1表示为整形的最大值,不会结束判断,就会导致死循环的发生。所以为了避免这种情况出现,判断条件写为end >=pos+len。之后将要插入的字符串依次拷贝即可。最后挪动_size。
erase函数,要实现在某个下标删除len个字符长度。如果要删除的字符长度大于该字符串之后的内容,以及len为缺省值npos,那么表示要将该下标位置之后的值全部删除。直接挪动_size为_size-len,并将_size位置的值赋值为'\0'。
如果只是删除一段位置,就需要依次向前挪动数据。将pos位置的值赋值为pos+len,pos自增指向下一个位置。循环结束条件为pos+len<=_size,表示已经遍历到最后一个数据。
2.6.5 find查找函数
cpp
size_t string::find(const char* s, size_t pos)const
{
assert(pos < _size);
char* str = strstr(_str, s);
if (str == nullptr)
{
return npos;
}
else
{
return str - _str;
}
}
size_t string::find(const char c, size_t pos)const
{
assert(pos < _size);
for (size_t i = 0; i < _size; i++)
{
if (_str[i] == c)
{
return i;
}
}
return npos;
}
查找函数实现了两个重载,分别表示在当前对象字符串的某个下标及之后的位置寻找字符串s,和寻找单字符c。
如果是寻找字符串s,利用了strstr函数,如果找到了,该函数会返回第一次出现该字符串的地址,减去首地址就得到了下标。如果没找到,就会返回空指针。
如果是寻找单字符,for循环遍历即可。
2.6.6 判断大小相关函数
cpp
bool string::operator==(const string& s)const
{
size_t l1 = 0;
size_t l2 = 0;
while (l1 < _size && l2 < s.size())
{
if (_str[l1] < s[l2])
{
return false;
}
else if (_str[l1] > s[l2])
{
return false;
}
else
{
l1++;
l2++;
}
}
return ((l1 == _size) && (l2 == s.size()));
}
bool string::operator!=(const string& s)const
{
return !((*this) == s);
}
bool string::operator<(const string& s)const
{
size_t l1 = 0;
size_t l2 = 0;
while (l1 < _size && l2 < s.size())
{
if (_str[l1] < s[l2])
{
return true;
}
else if (_str[l1] > s[l2])
{
return false;
}
else
{
l1++;
l2++;
}
}
return (l2 < s.size());
}
bool string::operator<=(const string& s)const
{
return (*this) < s || (*this) == s;
}
bool string::operator>(const string& s)const
{
return !((*this) <= s);
}
bool string::operator>=(const string& s)const
{
return (*this) > s || (*this) == s;
}
判断大小相关的函数,只需要实现判断相等和判断小于,即可函数复用来实现其他相关函数。
首先是判断相等:遍历两个字符串中的每个字符,如果出现不相等的情况,返回false;相等的情况,两个下标均向后走。最后出了循环之后,当两个下标位置均走到了两个字符串的末尾,表示两个字符串长度相同,并且每个字符都相同,返回true。
之后是判断小于,依次遍历每个字符,在中间过程中,只要出现了一次字符小于另一个,就直接返回true,出现大于返回false。两个字符相同的情况均向后走。循环结束后,会出现三种情况:1. 字符串s1长度小于字符串s2,此时指向s2的下标小于s2的size,返回true;2.字符串s1的长度和s2相等,此时返回false;3.字符串s1的长度大于s2,也返回false。所以返回判断条件可以只写return (l2 < s.size());
2.6.7 流插入 和 流提取函数的重载
cpp
ostream& operator << (ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); i++)
{
cout << s[i];
}
return out;
}
void string::clear()
{
_str[0] = '\0';
_size = 0;
}
//以下的两种写法会涉及到开辟空间多次的问题
//istream& operator>>(istream& in, string& s)
//{
// s.clear();
// char ch = in.get();
// while (ch != ' ' && ch != '\n')
// {
// s += ch;
// ch = in.get();
// }
// return in;
//}
istream& operator>>(istream& in, string& s)
{
s.clear();
char tmp[256];
size_t i = 0;
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
tmp[i++] = ch;
if (i == 255)
{
tmp[i] = '/0';
s += tmp;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
tmp[i] = '\0';
s += tmp;
}
return in;
}
//istream& getline(istream& in, string& s, char delim)
//{
// s.clear();
// char ch = in.get();
// while (ch != delim)
// {
// s += ch;
// ch = in.get();
// }
// return in;
//}
istream& getline(istream& in, string& s, char delim)
{
s.clear();
char tmp[256];
size_t i = 0;
char ch = in.get();
while (ch != delim)
{
tmp[i++] = ch;
if (i == 255)
{
tmp[i] = '/0';
s += tmp;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
tmp[i] = '\0';
s += tmp;
}
return in;
}
}
首先是流提取运算符,在之前的一个小练习日期类的实现过程中, 流插入和提取均实现为了友元函数,因为要访问私有成员变量。而在这里,可以通过c_str函数访问到_str这个字符数组指针,所以可以不用设置为私有。同时,由于\[\]运算符重载的存在,可以通过\[\]下标直接访问,依次输出每个字符即可。
针对流提取运算符的重载,利用插入流cin中内部函数get。该函数可以提取到空格和换行符。整体思路为:输入一串字符,利用一个字符变量来接收字符,如果该字符不是空格或者换行符,将其插入到string对象的末尾,之后再次接收该字符,直到循环结束。geiline也是类似的思路,只是将循环判断结束的条件变为手动指定的标识符。
上述方法,由于输入的字符长度不确定,如果输入字符长度过长,会导致多次扩容的问题,可能会导致其效率降低。针对此问题,对流插入和geiline函数进行一定程度的优化:
首先创造一个字符串数组,可以将其开辟的大一些。然后在这个字符串数组中不断地尾插字符,如果该字符到了最后一位,则在最后一位插入'\0',然后将其插入到string对象中;之后重新赋值该数组。如果遇到了循环结束的条件,则直接在该数组的当前位置插入'\0',尾插到string对象中。该方法可以降低string对象扩容的次数。
2.6.7 拷贝构造函数和赋值运算符重载的现代写法
void string::Swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string::string(const char* str)
{
string tmp(str);
Swap(tmp);
}
string& string::operator=(string s)
{
Swap(s);
return *this;
}
首先实现了一个交换函数,用来交换两个string对象之间的数组地址、_size和_capacity。
拷贝构造,利用要拷贝对象的_str构造一个临时对象tmp,只需要调用tmp函数,将这个临时对象的成员变量换到this对象中即可,可完成拷贝构造。
赋值运算符重载,传参只需要传一个string对象s,注意不需要引用,然后调用Swap交换即可。由于传递的形参不是引用类型,是原对象的一个拷贝,所以能在完成赋值的情况下,不影响原对象。
至此,完成了string类的基础实现,虽说和库中的string类仍存在不小的差别,但整体的实现思路和对于类和对象的掌握与理解更进一步得到了加深,尤其是构造、拷贝构造、命名空间、数据的增删查改、数据边界等都有了进一步的巩固加深。