C++ 自己实现一个 string 类

引言:

在 C++ 中,标准库提供了强大的 std::string 类,它封装了字符串的大部分操作,使得使用字符串变得非常方便。尝试自己实现 string 类,深入理解字符串的底层实现机制,不仅可以帮助我们更好地使用 std::string,还能帮助我们深入理解动态内存管理、掌握深拷贝和深赋值的概念、提升对字符串操作的理解。

一、类的设计与接口

1、命名空间的使用

使用 namespace bit 避免与标准库string类冲突

2、类的成员变量
char* _str:动态分配的字符数组,用于存储字符串
size_t _size:当前字符串的长度
size_t _capacity:分配的内存容量
3、默认成员函数(构造函数、析构函数、拷贝构造函数、赋值重载函数)

构造函数:

默认构造函数:初始化为空字符串

带参构造函数:接受 const char*,动态分配内存并复制内容

析构函数:释放动态分配的内存

拷贝构造函数:实现深拷贝

赋值重载:实现深赋值,防止自赋值

4、遍历:下标+[]、迭代器支持

重载[ ]以提供下标+[]形式的遍历

提供 iteratorconst_iterator支持迭代器

5、其他接口(增删查、常用运算符重载)

reverse:预留内存
push_back、append、+=:添加字符或字符串
insert、erase:插入和删除操作
find:查找字符或子串
substr:获取子字符串
c_str、size、clear:辅助功能
常用运算符重载

二、 实现细节

1、文件创建

创建string.h文件string.cpp文件实现声明与定义的分离,同时使用命名空间,避免与标准库中的string类冲突

string.h和string.cpp内容的大致结构

//string.h内容
#include<iostream>
#include<assert.h>
using namespace std;

//使用命名空间封装避免和库里的冲突
namespace bit
{
	class string
	{
	public:
    //各类接口声明...
    private:
	    char* _str;
	    size_t _size;
	    size_t _capacity;
}

从上面的声明可以看出,string类的底层是一个和顺序表极其类似的结构,或者说字符串本质上是一种特殊的顺序表,其中的元素是字符

三个成员变量:char*  _str   size_t  _size   size_t  _capacity

char*  _str :指向字符数组的指针,存储实际的字符串数据,是一个动态分配的内存区域,用于存储字符串的内容。通过它可以动态分配内存,可根据需要调整大小。但是需要手动管理内存分配和释放,例如在构造函数、析构函数和拷贝构造函数中进行操作

size_t  _size:记录当前字符串的实际长度(不包括末尾的空字符\0),它也是动态的,随着字符串内容的改变而改变。用于快速获取字符串长度,避免每次调用长度函数进行计算

size_t  _capacity:记录分配给_str的内存容量(以字符为单位),_capapcity表示分配的内存可以容纳的字符总数(包括末尾的\0)。它用于优化内存分配,避免频繁的内存重新分配,以及判断是否需要重新分配更大的内存

//string.cpp
#include"string.h"

namespace bit
{
    //接口的具体实现
}

 我们将实现较为复杂的接口,在上面的.cpp文件中实现,而较短、较简单的函数则在.h文件直接实现定义

2、具体实现
2.1 默认成员函数实现

先来实现默认成员函数:构造函数、析构函数、拷贝构造函数、赋值运算符重载

在string.h文件中加上三个函数的声明:

//构造函数
string(const char* str = "");
//析构函数
~string();
//拷贝构造函数
string(const string& s);
//赋值运算符重载
string& operator=(const string& s);

 在string.cpp文件中对四个函数进行具体实现:

1 构造函数:

声明:

//构造函数
string(const char* str = "");

解释:构造函数是用来初始化对象的,对于构造函数来说,一般需要实现两类:带参构造、无参构造,因为初始化对象时,有时会给初始值有时不会,所以在声明时我们给出了缺省值"",也就是const char* str = "",这样如果用户没有给初始值,会自动初始化为"",也就是空 

实现: 

//未优化(普通)版,使用初始化列表
string(const char*str)
    :_str(new char[strlen(str)+1])
    :_size(strlen(str))
    :_capacity(strlen(str))
{}

在写构造函数时,初始化成员变量除了使用函数体内赋值,还可以使用初始化列表的方式,我们尽量使用初始化列表的方式,因为那些你不在初始化列表初始化的成员也会走初始化列表;但是上方的写法不好,因为strlen是一个O(N)的函数,上面一下就使用了三个O(N),这会对函数有着极大的效率影响,所以将其优化成了下面的版本⬇

//构造函数
string::string(const char* str)
	:_size(strlen(str))
{
	_capacity = _size;
	_str = new char[_size + 1];//+1给\0预留位置
	strcpy(_str, str);
}

解释:为了改善strlen带来的效率问题,我们使用了初始化列表,只调用strlen一次,获取string的长度,将其初始化赋值给_size,再在函数体内利用_size的值初始化_capacity和_str,在给_str开辟空间时,记得多初始化一个空间给\0留位置,最后用strcpy函数将值拷贝进_str的空间

2 析构函数

声明:

//析构函数
~string();

实现:

string::~string()
{
	delete[] _str;
	_str = nullptr;
	_size = 0;
	_capacity = 0;
}

因为我们定义的结构是有动态分配内存的,所以在结束时也需要析构函数来释放空间 

3 拷贝构造函数

声明:

//拷贝构造函数
string(const string& s);

注意:拷贝构造函数的第一个参数必须是该类型的引用

实现:

string::string(const string& s)
{
	_str = new char[s._capacity + 1];
	strcpy(_str, s._str);
	_size = s._size;
	_capacity = s._capacity;

}

我们需要先拷贝一块和被拷贝者同样大小的空间即_str = new char[s._capacity + 1],再拷贝相同的内容,这样拷贝后的_str指向的是一个独立的内存空间,而不是两者指向同一块空间,即深拷贝

不能是浅拷贝,必须完成深拷贝,也就是拷贝完后拷贝方和被拷贝方指向的是两个不同的空间,需要重新开辟一块同样大小的空间,再将内容拷贝给新的空间

4  赋值运算符重载

声明:

//赋值运算符重载
string& operator=(const string& s);

注意:返回值返回的是引用

实现:

string& string::operator=(const string& s)
{
	//避免自己给自己赋值的情况
	if (this != &s)
	{
		//释放原来的空间
		delete[]_str;
		//开辟同样大小的空间
		_str = new char[s._capacity + 1];//多开一个预留给\0
		strcpy(_str, s._str);
	}
	return *this;
}

因为赋值时会先释放自己原来的空间,如果出现自己给自己赋值的情况,因为自己原来的空间被释放了,那就没办法获取到值给自己赋值,所以会先判断是否是给自己赋值

和拷贝构造一样,赋值运算符也必须是深赋值,即两方指向两块不同的空间

2.2 遍历的实现
1 下标+[ ]形式的遍历

因为函数内容较短,我们能就直接实现在声明文件(string.h)中

//[]的普通版本,返回的是引用
char& operator[](size_t i)
{
	assert(i < _size);

	return _str[i];
}
//[]的const版本
const char& operator[](size_t i) const
{
	assert(i < _size);

	return _str[i];
}

注意:有时候只需要遍历不需要修改值时就要使用const

先判断访问的下标是否合适,再通过指针解引用的操作返回该下标查找的值

2 迭代器形式的遍历

 先使用using语法定义类型别名,

  1. iterator:指向char的指针,可以用于读写操作

  2. const_iterator:指向const char的指针,只能用于读取操作

//需要先定义迭代器类型
using iterator = char*;//迭代器
using const_iterator = const char*;//const迭代器
//用原身指针返回迭代器
//返回开始位置
iterator begin()
{
	return _str;
}
//返回最后一个位置
iterator end()
{
	return _str + _size;
}
//const版本
const_iterator begin() const
{
	return _str;
}
const_iterator end() const
{
	return _str + _size;
}

注意const版本的写法为:const_iterator begin() const ,因为指向的内容不可以修改,不是迭代器不能修改,迭代器要++,所以不是这样声明const iterator begin() const

2.3 增删查改的实现
1 增(插入)

声明:

//插入部分
void reserve(size_t n);//预留内存函数
void push_back(char ch);
void append(const char* str);
string& operator+=(char ch);
string& operator+=(const char* str);

void insert(size_t pos, char ch);
void insert(size_t pos, const char* str);

实现:

reserve:

插入时会涉及空间不够的问题,也就是需要扩容,这里我们通过实现reserve函数来进行扩容,reserve函数的作用就是通过传递空间大小参数n来让其开设n大小的空间,如果n大于现有的空间大小,就进行扩容,然后将原来空间的值拷贝到新的空间,再将原来的空间进行释放,将新的空间赋值给对象

//借助reserve来扩容
void string::reserve(size_t n)
{
	if (n > _capacity)
	{
		//记住:开空间一定要多开一个
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;

		_capacity = n;
	}
}

 push_back 和 append:

 push_back用来插入单个字符,append用来插入长的字符串,注意两者只能尾插

//插入单个字符
void string::push_back(char ch)
{
	//空间满了就扩容
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	_str[_size] = ch;//从尾部插入ch
	_size++;//长度++
}
//插入字符串
void string::append(const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		size_t newCapacity = 2 * _capacity;
		// 扩2倍不够,则需要多少扩多少
		if (newCapacity < _size + len)
			newCapacity = _size + len;

		reserve(newCapacity);
	}
	//拷贝
	strcpy(_str + _size, str);//在末尾将字符串的内容拷贝进来
	_size += len;
}

 重载+=运算符:

 在实现+=时,可以直接复用push_back 和 append,同样+=也有插入字符和字符串两种情况

string& string::operator+=(char ch)
{
	push_back(ch);

	return *this;
}
string& string::operator+=(const char* str)
{
	append(str);

	return *this;
}

insert:实现任意位置的插入

insert插入字符:

定义end指向\0的下一个位置,在end走到pos(待插入的位置)之前,将end-1位置上的数据移动到end的位置上,最后在pos位置插入需要插入的字符ch

//pos位置插入一个字符
void string::insert(size_t pos, char ch)
{
    //判断插入位置是符合法
	assert(pos <= _size);
    //空间不够插入就扩容
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
    //让end指向\0的下一个位置
	size_t end = _size + 1;
	while (end>pos)
	{
		_str[end] = _str[end - 1];
		end--;
	}
	_str[pos] = ch;
	_size++;
}

insert插入字符串:

定义end到插入字符串后,字符串最末尾的位置

void string::insert(size_t pos, const char* str)
{
	assert(pos <= _size);
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		size_t newCapacity = 2 * _capacity;
		// 扩2倍不够,则需要多少扩多少
		if (newCapacity < _size + len)
			newCapacity = _size + len;
		reserve(newCapacity);
	}
    //定义到+上插入字符串后最末尾的位置
	size_t end = _size + len;
	while (end > pos+len-1)
	{
		_str[end] = _str[end-len];
		--end;
	}
	for (size_t i = 0; i < len; i++)
	{
		_str[pos + i] = str[i];
	}
	_size+=len;
}
2 删

 声明:

erase:在pos位置删除len个字符

void erase(size_t pos, size_t len=npos);

注:len给了一个缺省参数npos,它在C++的std::string类里是一个特殊的常量,用来表示“无效位置”或者“最大可能位置”,这里使用npos作为缺省参数表示,如果用户没有键入第二个参数,就将从pos位置开始往后的所有内容全部删除

 所以在我们自定义的类中还应该加上npos的声明定义:

//string.h
public:
	static const size_t npos;
//string.cpp
const size_t string::npos=-1;

实现:

会有两种情况:
①删除的部分大于整个字符串的长度

②只删除字符串内的一部分

void string::erase(size_t pos, size_t len)
{
	assert(pos < _size);
    //删除部分大于原来的长度
	if (len >= _size - pos)
	{
		_str[pos] = '\0';
		_size = pos;
	}else
	{   //只删除一部分
		size_t end = pos + len;
		while (end<=_size)
		{
			//end的值从后往前挪
			_str[end - len] = _str[end];
			++end;
		}
		_size -= len;
	}
}
 3 查

声明:

find:在pos位置查找想要的内容

size_t find(char ch, size_t pos = 0);//从pos位置查找字符
size_t find(const char* str, size_t pos=0);//从pos位置查找子串

pos都默认给了缺省值0,当用户没有键入第二个参数时,默认从开头的位置开始找 

 实现:

//查找字符
size_t string::find(char ch, size_t pos)
{
	assert(pos < _size);
	//要从pos位置遍历找
	for (size_t i = pos; i < _size; i++)
	{
		if (ch == _str[i])
		{
			return i;
		}
	}
	return npos;
}
//查找子串
size_t string::find(const char* str, size_t pos)
{
	assert(pos < _size);
	//返回str在_str+pos第一次出现的位置
	const char* ptr=strstr(_str+pos, str);
	if (ptr == nullptr)
	{
		return npos;
	}
	else 
	{
        //返回索引值
		return ptr - _str;
	}
}
4 改

因为“改”通过拷贝构造函数和赋值运算符重载或者遍历改值就可以,直接看前面的实现即可

2.4 运算符重载

声明:

bool operator==(const string& lhs, const string& rhs);
bool operator!= (const string& lhs, const string& rhs);
bool operator> (const string& lhs, const string& rhs);
bool operator< (const string& lhs, const string& rhs);
bool operator>= (const string& lhs, const string& rhs);
bool operator<= (const string& lhs, const string& rhs);

注:上面的运算符重载都定义为全局函数,非成员函数,因为如果设计为成员函数会固定第一个参数的类型

在实现之前我们需要先实现一个辅助功能c_str,它的功能是返回C格式的字符串,作用是提供了一种将C++的std::string对象转换为C语言风格字符串的机制,从而使得std::string可以与C语言库函数无缝兼容,也即是让C++的string对象可以使用C语言库中的函数

const char* c_str() const
{
	return _str;
}

实现:

bool operator== (const string& lhs, const string& rhs)
{
	return strcmp(lhs.c_str(), rhs.c_str()) == 0;
}
bool operator!= (const string& lhs, const string& rhs)
{
	return !(lhs == rhs);
}
bool operator> (const string& lhs, const string& rhs)
{
	return !(lhs <= rhs);
}
bool operator< (const string& lhs, const string& rhs)
{
	return strcmp(lhs.c_str(), rhs.c_str()) < 0;
}
bool operator>= (const string& lhs, const string& rhs)
{
	return !(lhs < rhs);
}
bool operator<= (const string& lhs, const string& rhs)
{
	return lhs < rhs || lhs == rhs;
}

在函数实现中我们使用了C语言的strcmp函数,所以需要使用c_str将其转换为C语言格式的字符串,同时我们又使用了复用的方式,将已实现的函数进行复用 

 2.4 其他接口+辅助功能
1 substr

功能:从字符串中提取一个子字符串,从pos位置提取长度为len的子字符串

声明:

string substr(size_t pos, size_t len=npos);

 实现:

string string::substr(size_t pos, size_t len)
{
	//查找的子串长度大于可查找(剩余)的部分
	//则直接取到结尾
	if (len > (_size - pos))
	{
		//更新子串长度
		len = _size - pos;
	}
	bit::string sub;
	sub.reserve(len);
	for (size_t i = 0; i < len; i++)
	{
		sub += _str[pos + i];
	}
	return sub;
}
 2 流插入和流提取重载

为了我们实现的string类可以在屏幕上进行输入和打印,还必须实现流插入和流提取符号的重载

声明

ostream& operator<<(ostream& os, const string& str);
istream& operator>>(istream& is, string& str);

is和os对象都必须传引用,不能传值,两者区别在于ostream要加const

实现:

ostream& operator<<(ostream& os, const string& str)
{
	for (size_t i = 0; i < str.size(); i++)
	{
		os << str[i];
	}
	return os;
}

流提取:
重载的输入流提取运算符>>,用于从输入流istream中读取一个字符串,并将其存储到自定义的string对象str

  • 输入

    • istream& is:输入流对象,通常是一个std::cin或文件输入流

    • string& str:引用到一个std::string对象,用于存储读取到的字符串

  • 输出

    • 返回值是istream&,即输入流的引用,这使得可以链式调用提取运算符

在实现流提取前,需要先实现另一个辅助功能clear,作用是将内容清空

void clear()
{
	_str[0] = '\0';
	_size = 0;
}
istream& operator>>(istream& is, string& str)
{
	//把数据清掉,不出现提取的结果是+在原来字符尾巴后面的情况
	str.clear();
	//一次开够,空间浪费,整一个buff
	int i = 0;
	char buff[256];
	char ch;
	ch = is.get();//为了可以读取空格
	while (ch != ' ' && ch != '\n')//遇到空格或者换行就停止提取
	{
		//放到buff,减少扩容,即用即销毁
		buff[i++] = ch;
		if (i == 255)
		{
			buff[i] = '\0';
			str += buff;
			i = 0;
		}
	}
	if (i > 0)
	{
		buff[i] = '\0';
		str += buff;
	}
	return is;
}

实现逻辑:

在读取新字符串之前,先使用str.clear();清空str的内容,确保不会将新读取的内容追加到旧内容的末尾。再定义一个缓冲区buff,大小为256个字符,用于临时存储读取的字符。使用i作为缓冲区的索引,记录当前缓冲区中存储的字符数量。

使用is.get()从输入流中逐字符读取,而不是is >> chis.get()会读取所有字符,包括空格和换行符,而is >> ch会跳过空格。循环读取字符,直到遇到空格或换行符为止。每次读取的字符存储到缓冲区buff中。如果缓冲区满了(i == 255),将缓冲区的内容追加到目标字符串str中,并重置缓冲区索引i。如果循环结束时缓冲区中还有未处理的字符(i > 0),将这些字符追加到目标字符串str中。

使用将缓冲区的内容追加到目标字符串str中的方式可以一次性扩容,减少了扩容带来的效率问题

三、完整展示

string.h文件:

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once

#include<iostream>
#include<assert.h>
using namespace std;

//使用命名空间封装避免和库里的冲突
namespace bit
{
	class string
	{
	public:
		//typedef char* iterator;
		using iterator = char*;//迭代器
		using const_iterator = const char*;//const迭代器

		//构造函数:不能初始化为空和0,防止空指针访问的问题
		//带参构造函数,最好是有全缺省的省去无参构造函数
		string(const char* str = "");//直接这样初始化就行,常量字符串默认给\0
		//析构函数
		~string();
		//拷贝构造函数
		string(const string& s);
		//赋值运算符重载
		string& operator=(const string& s);

		//插入部分
		void reserve(size_t n);
		void push_back(char ch);
		void append(const char* str);
		string& operator+=(char ch);
		string& operator+=(const char* str);

		void insert(size_t pos, char ch);
		void insert(size_t pos, const char* str);
		void erase(size_t pos, size_t len=npos);

		//查找
		size_t find(char ch, size_t pos = 0);
		size_t find(const char* str, size_t pos=0);

		//遍历部分
		//[]的普通版本,返回的是引用
		char& operator[](size_t i)
		{
			assert(i < _size);

			return _str[i];
		}
		//[]的const版本
		const char& operator[](size_t i) const
		{
			assert(i < _size);

			return _str[i];
		}

		//用原身指针返回迭代器
		//返回开始位置
		iterator begin()
		{
			return _str;
		}

		//返回最后一个位置
		iterator end()
		{
			return _str + _size;
		}

		//指向的内容不可以修改,不是迭代器不能修改,迭代器要++
		//所以不是这样声明const iterator begin() const
		const_iterator begin() const
		{
			return _str;
		}

		const_iterator end() const
		{
			return _str + _size;
		}

		//返回大小
		size_t size() const
		{
			return _size;
		}

		//短的就直接写在声明里
		const char* c_str() const
		{
			return _str;
		}

		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}

		string substr(size_t pos, size_t len=npos);
	private:
		char* _str;
		size_t _size;
		size_t _capacity;

	public:
		static const size_t npos;
	};

    //全局函数
	bool operator==(const string& lhs, const string& rhs);
	bool operator!= (const string& lhs, const string& rhs);
	bool operator> (const string& lhs, const string& rhs);
	bool operator< (const string& lhs, const string& rhs);
	bool operator>= (const string& lhs, const string& rhs);
	bool operator<= (const string& lhs, const string& rhs);

	//流插入和流提取
	ostream& operator<<(ostream& os, const string& str);
	istream& operator>>(istream& is, string& str);

}

string.cpp文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"

namespace bit
{
	const size_t string::npos=-1;

	//有参,全缺省,缺省只能在声明写
	string::string(const char* str)
		:_size(strlen(str))
	{
		_capacity = _size;
		_str = new char[_size + 1];//+1给\0预留位置
		strcpy(_str, str);
	}

	string::~string()
	{
		delete[] _str;
		_str = nullptr;
		_size = 0;
		_capacity = 0;
	}
	//拷贝构造
	string::string(const string& s)
	{
		_str = new char[s._capacity + 1];
		strcpy(_str, s._str);
		_size = s._size;
		_capacity = s._capacity;

	}


	// s1=s2=s3
	// s1=s3,s就是s3
	string& string::operator=(const string& s)
	{
		//避免自己给自己赋值的情况
		if (this != &s)
		{
			//释放原来的空间
			delete[]_str;
			//开辟同样大小的空间
			_str = new char[s._capacity + 1];//多开一个预留给\0
			strcpy(_str, s._str);
		}
		return *this;
	}

	//借助reserve来扩容
	void string::reserve(size_t n)
	{
		if (n > _capacity)
		{
			//记住:开空间一定要多开一个
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;

			_capacity = n;
		}
	}

	
	void string::push_back(char ch)
	{
		//满了
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		_str[_size] = ch;
		_size++;

		//insert写好,push_back是可以复用的,相当于不用写push_back
		//insert(_size, ch);
	}
	
	void string::append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			size_t newCapacity = 2 * _capacity;
			// 扩2倍不够,则需要多少扩多少
			if (newCapacity < _size + len)
				newCapacity = _size + len;

			reserve(newCapacity);
		}
		//拷贝
		strcpy(_str + _size, str);
		_size += len;
	}

	string& string::operator+=(char ch)
	{
		push_back(ch);

		return *this;
	}

	string& string::operator+=(const char* str)
	{
		append(str);

		return *this;
	}

	//pos位置插入一个字符
	void string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);

		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		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* str)
	{
		assert(pos <= _size);

		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			size_t newCapacity = 2 * _capacity;
			// 扩2倍不够,则需要多少扩多少
			if (newCapacity < _size + len)
				newCapacity = _size + len;

			reserve(newCapacity);
		}
		size_t end = _size + len;
		while (end > pos+len-1)
		{
			_str[end] = _str[end-len];
			--end;
		}
		for (size_t i = 0; i < len; i++)
		{
			_str[pos + i] = str[i];
		}
		_size+=len;
	}
	//从pos位置删除len个字符
	void string::erase(size_t pos, size_t len)
	{
		assert(pos < _size);
		if (len >= _size - pos)
		{
			_str[pos] = '\0';
			_size = pos;
		}else{  //只删除一部分
			size_t end = pos + len;
			while (end<=_size)
			{
				//end的值从后往前挪
				_str[end - len] = _str[end];
				++end;
			}
			_size -= len;
		}
	}
	//查找字符
	size_t string::find(char ch, size_t pos)
	{
		assert(pos < _size);
		//要从pos位置开始找
		for (size_t i = pos; i < _size; i++)
		{
			if (ch == _str[i])
			{
				return i;
			}
		}
		return npos;
	}
	//查找子串
	size_t string::find(const char* str, size_t pos)
	{
		assert(pos < _size);
		//返回str在_str+pos第一次出现的位置
		const char* ptr=strstr(_str+pos, str);
		if (ptr == nullptr)
		{
			return npos;
		}
		else 
		{
			return ptr - _str;
		}
	}

	string string::substr(size_t pos, size_t len)
	{
		//查找的子串长度大于可查找(剩余)的部分
		//则直接取到结尾
		if (len > (_size - pos))
		{
			//更新子串长度
			len = _size - pos;
		}
		bit::string sub;
		sub.reserve(len);
		for (size_t i = 0; i < len; i++)
		{
			sub += _str[pos + i];
		}
		//cout << sub.c_str() << endl;
		return sub;
	}


	////////////////////////////////////////////////////////////////////

	bool operator== (const string& lhs, const string& rhs)
	{
		return strcmp(lhs.c_str(), rhs.c_str()) == 0;
	}

	bool operator!= (const string& lhs, const string& rhs)
	{
		return !(lhs == rhs);
	}

	bool operator> (const string& lhs, const string& rhs)
	{
		return !(lhs <= rhs);
	}

	bool operator< (const string& lhs, const string& rhs)
	{
		return strcmp(lhs.c_str(), rhs.c_str()) < 0;
	}

	bool operator>= (const string& lhs, const string& rhs)
	{
		return !(lhs < rhs);
	}

	bool operator<= (const string& lhs, const string& rhs)
	{
		return lhs < rhs || lhs == rhs;
	}

	ostream& operator<<(ostream& os, const string& str)
	{
		//可以自定义打出来的格式
		//os<<'"';
		//os << "xx\"xx";
		for (size_t i = 0; i < str.size(); i++)
		{
			os << str[i];
		}
		//os << '"';

		return os;
	}

	istream& operator>>(istream& is, string& str)
	{
		//把数据清掉
		//不出现提取的结果是+在原来字符尾巴后面的情况
		str.clear();
		//一次开够,空间浪费
		//str.reserve(1024);
		//整一个buff
		int i = 0;
		char buff[256];

		char ch;
		//is >> ch;//默认读到空格就丢弃,就会拿不到字符串的空格
		ch = is.get();//这样就会读取到空格
		while (ch != ' ' && ch != '\n')//遇到空格或者换行就停止提取
		{
			/*str += ch;
			ch = is.get();*/

			//放到buff,减少扩容,即用即销毁
			buff[i++] = ch;
			if (i == 255)
			{
				buff[i] = '\0';
				str += buff;
				i = 0;
			}
		}
		if (i > 0)
		{
			buff[i] = '\0';
			str += buff;
		}
		return is;
	}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值