STL--string

目录

【本节目标】

1.为什么学习string类?

1.1C语言中的字符串

1.2两个面试题(暂不做讲解)

2.标准库中的string类

2.1 string类(了解)

2.2 string类的常用接口说明(注意下面我只讲解最常用的接口)

2.3 string类(cplusplus)

Iterators(迭代器):

 Element access:

Modifiers: 

String operations: 

3.string类的模拟实现

3.1经典的string类问题

3.2 浅拷贝

3.3 深拷贝

3.3.1 传统版写法的String类

3.3.2 现代版写法的String类

3.3 写时拷贝(了解)

3.4 模拟实现


【本节目标】

1. 为什么要学习string类
2. 标准库中的string类
3. string类的模拟实现
4. 扩展阅读

1.为什么学习string类?

1.1C语言中的字符串

C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

1.2两个面试题(暂不做讲解)

字符串转整形数字
字符串相加

在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。

2.标准库中的string类

2.1 string类(了解)

1. 字符串是表示字符序列的类
2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。
3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信息,请参阅basic_string)。
4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。
5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。

总结:
1. string是表示字符串的字符串类
2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。

3. string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator>string;
4. 不能操作多字节或者变长字符的序列。

注意:

在使用string类时,必须包含#include头文件以及using namespace std;

2.2 string类的常用接口说明(注意下面我只讲解最常用的接口)

1. string类对象的常见构造

(constructor)函数名称功能说明
string() (重点)构造空的string类对象,即空字符串
string(const char* s) (重点)用C-string来构造string类对象
string(size_t n, char c)string类对象中包含n个字符c
string(const string&s) (重点)拷贝构造函数
void test_string1()
{
	// 21:16
	string s0;
	string s1("hello world");
	string s2(s1);
	string s3(s1, 5, 3);
	string s4(s1, 5, 10);
	string s5(s1, 5);

	cout << s0 << endl;
	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;
	cout << s5 << endl;

	string s6(10, '#');
	cout << s6 << endl;

	s0 = s6;
	cout << s0 << endl;
}

s0为默认构造

s1为单参数构造

s2为拷贝构造

s3为字符串中的只构造s1中的一部分(已经构造出的对象,字符串中的位置,从此位置后框选字符的长度),s4中框选的长度可以更大,如果超出范围,则框选从选中位置之后的所有字符,s5中没有第三参数时和s4相同。

s6为初始化为##########

2. string类对象的容量操作 

函数名称功能说明
size(重点)返回字符串有效字符长度
length返回字符串有效字符长度
capacity返回空间总大小
empty (重点)检测字符串释放为空串,是返回true,否则返回false
clear (重点)清空有效字符
reserve (重点)为字符串预留空间**
resize (重点)将有效字符的个数该成n个,多出的空间用字符c填充

注意:
1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
2. clear()只是将string中有效字符清空,不改变底层空间大小。
3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。

3. string类对象的访问及遍历操作 

函数名称功能说明
operator[] (重
点)
返回pos位置的字符,const string类对象调用
begin+ endbegin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭
代器
rbegin + rendbegin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭
代器
范围forC++11支持更简洁的范围for的新遍历方式

第一种遍历:for循环遍历

string s1("hello world");

// 第一种遍历
// 下标+[]
for (size_t i = 0; i < s1.size(); i++)
{
	cout << s1[i] << " ";
	//cout << s1.operator[](i) << " ";
}
cout << endl;

for (size_t i = 0; i < s1.size(); i++)
{
	s1[i]++;
}
cout << endl;

for (size_t i = 0; i < s1.size(); i++)
{
	cout << s1[i] << " ";
}
cout << endl;

第二种遍历:迭代器(迭代器中begin与end之间不包含\0)

string s3("hello world");
string::iterator it3 = s3.begin();
while (it3 != s3.end())
{
	*it3 -= 3;
	++it3;
}
cout << endl;

it3 = s3.begin();
while (it3 != s3.end())
{
	cout << *it3 << " ";
	++it3;
}
cout << endl;
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);

vector<int>::iterator it = v.begin();
while (it != v.end())
{
	cout << *it << " ";
	++it;
}
cout << endl;

list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);

list<int>::iterator itt = lt.begin();
while (itt != lt.end())
{
	cout << *itt << " ";
	++itt;
}
cout << endl;

第三种遍历: 

string s3("hello world");
// 底层就是迭代器
for (auto e : s3)
{
	cout << e << " ";
}
cout << endl;

for (auto e : v)
{
	cout << e << " ";
}
cout << endl;

for (auto e : lt)
{
	cout << e << " ";
}
cout << endl;

 4. string类对象的修改操作 

函数名称功能说明
push_back在字符串后尾插字符c
append在字符串后追加一个字符串
operator+= (重点)在字符串后追加字符串str
c_str(重点)返回C格式字符串
find + npos(重点)从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
rfind从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
substr在str中从pos位置开始,截取n个字符,然后将其返回

注意:
1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。

5. string类非成员函数 

函数功能说明
operator+尽量少用,因为传值返回,导致深拷贝效率低
operator>> (重点)输入运算符重载
operator<< (重点)输出运算符重载
getline (重点)获取一行字符串
relational operators (重点)大小比较

上面的几个接口大家了解一下,下面的OJ题目中会有一些体现他们的使用。string类中还有一些其他的操作,这里不一一列举,大家在需要用到时不明白了查文档即可。

class string
{
public:
	char& operator[](size_t pos)
	{
		return _str[pos];
	}

private:
	char* _str;
	size_t _size;
	size_t _capacity;
};

2.3 string类(cplusplus)

Iterators(迭代器):
begin顺序的,元素最开始的位置
end顺序的,元素最后的位置(不包含\0)
rbegin逆序的,元素从最末尾的位置开始
rend逆序的,元素从最开始的位置结束
cbegin顺序的,元素最开始的位置(const类型的对象)
cend顺序的,元素最后的位置(不包含\0)(const类型的对象)
crbegin逆序的,元素从最末尾的位置开始(const类型的对象)
crend逆序的,元素从最开始的位置结束(const类型的对象)

 begin         end

// 可读可写
string::iterator it2 = s1.begin();
while (it2 != s1.end())
{
	*it2 += 3;

	cout << *it2 << " ";
	++it2;
}
cout << endl;

rbegin      rend

string s1("hello world");
string::reverse_iterator rit = s1.rbegin();
while (rit != s1.rend())
{
	cout << *rit << " ";
	++rit;
}
cout << endl;

cbegin        cend

// 只读
const string s3("hello world");
string::const_iterator it3 = s3.cbegin();
while (it3 != s3.cend())
{
	// *it3 += 3;

	cout << *it3 << " ";
	++it3;
}
cout << endl;

crbegin            crend

// 只读
const string s3("hello world");
string::const_iterator it3 = s3.crbegin();
while (it3 != s3.crend())
{
	// *it3 += 3;

	cout << *it3 << " ";
	++it3;
}
cout << endl;

Capacity(容量):

size对象字符串的长度(常用)
length对象字符串的长度
max_sizestring字符串所能达到的最大长度
resize修改对象字符长度,可加相同的n个字符,可将字符减少到相应的字符长度,大于容量会扩容
capacity对象字符开辟的容量
reserve提前开辟一定的空间
clear清空对象中的字符
empty字符串是否为空

shrink_to_fit

会将空间容量适应对象的字符串大小(即capacity==size)

size   length     max_size      capacity

string s1("hello worldxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
cout << s1.size() << endl;
cout << s1.length() << endl;

cout << s1.max_size() << endl;
cout << s1.capacity() << endl;

capacity扩容规则

string s;

size_t sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
cout << "making s grow:\n";
for (int i = 0; i < 100; ++i)
{
	s.push_back('c');
	if (sz != s.capacity())
	{
		sz = s.capacity();
		cout << "capacity changed: " << sz << '\n';
	}
}

cout << s1 << endl;
cout << s1.capacity() << endl;
cout << s1.size() << endl;

shrink_to_fit

// 缩容
s1.shrink_to_fit();
cout << s1.capacity() << endl;
cout << s1.size() << endl;
 Element access:
operator[][]的运算符重载
atat在字符串中的位置的字符
back最后一个字符
front第一个字符

at

string s1("xhello world");
cout << s1[5] << endl;
cout << s1.at(5) << endl;
Modifiers
operator+=+=运算符重载
append尾插多个字符
push_back尾插单个字符
assign给出的字符串覆盖对象所有字符串

insert

在pos位置插入字符串
erase出去pos位置几个字符,减少字符长度
replace将pos位置上的字符可替换为多个字符
swap交换对象字符串
pop_back

push_back        append

string s1("xhello world");
s1.push_back('!');
cout << s1 << endl;

s1.append("hello bit");
cout << s1 << endl;

s1.append(10, 'x');
cout << s1 << endl;

string s2("  apple ");
//s1.append(s2);
//cout << s1 << endl;
s1.append(++s2.begin(), --s2.end());
cout << s1 << endl;

string s3("hello world");
s3 += ' ';
s3 += "apple";
cout << s3 << endl;

assign     insert      erase    replace      swap

string s1("xhello world");
cout << s1 << endl;

s1.assign(" xxxxx");
cout << s1 << endl;

s1.insert(0, "yyyy");
cout << s1 << endl;

s1.erase(5, 3);
cout << s1 << endl;

s1.erase();
cout << s1 << endl;

// 21:10继续
string s2("hello world hello bit");
s2.replace(5, 1, "20%");
cout << s2 << endl;

/*size_t pos = s2.find(' ');
while (pos != string::npos)
{
	s2.replace(pos, 1, "20%");
	pos = s2.find(' ');
}
cout << s2 << endl;*/

// insert/erase/replace
// 能少用就要少用,因为基本都要挪动数据,效率不高
string s3;
s3.reserve(s2.size());
for (auto ch : s2)
{
	if (ch != ' ')
	{
		s3 += ch;
	}
	else
	{
		s3 += "20%";
	}
}
cout << s3 << endl;
s2.swap(s3);
cout << s2 << endl;

insert/erase/replace
能少用就要少用,因为基本都要挪动数据,效率不高

String operations
c_str将C++中string对象字符兼容C语言
data获得string数据
get_allocator获得内存池
copy从对象字符串中拷贝部分字符到数组中
find顺序找到字符在字符串中的位置
rfind倒序找到字符在字符串中的位置
find_first_of顺序找到匹配多个字符的位置
find_last_of倒序找到匹配多个字符的位置
find_first_not_of顺序找到不匹配多个字符的位置
find_last_not_of倒序找到不匹配多个字符的位置
substr呈现从当前字符位置开始长度的字符串
compare比较

c_str        由于string对象中字符串可以不用\0,但c语言中必须有

string s1("hello world");

string filename("test.cpp");
FILE* fout = fopen(filename.c_str(), "r")

rfind

//string s1("file.cpp");
string s1("file.c.tar.zip");

// 拿到文件的后缀
size_t pos1 = s1.rfind('.');
if (pos1 != string::npos)
{
	string suffix = s1.substr(pos1);
	//string suffix = s1.substr(pos1, s1.size()-pos1);

	cout << suffix << endl;
}
else
{
	cout << "没有后缀" << endl;
}

find

string url2("https://legacy.cplusplus.com/reference/string/string/substr/");
string url1("http://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=65081411_1_oem_dg&wd=%E5%90%8E%E7%BC%80%20%E8%8B%B1%E6%96%87&fenlei=256&rsv_pq=0xc17a6c03003ede72&rsv_t=7f6eqaxivkivsW9Zwc41K2mIRleeNXjmiMjOgoAC0UgwLzPyVm%2FtSOeppDv%2F&rqlang=en&rsv_dl=ib&rsv_enter=1&rsv_sug3=4&rsv_sug1=3&rsv_sug7=100&rsv_sug2=0&rsv_btype=i&inputT=1588&rsv_sug4=6786");

string protocol, domain, uri;
size_t i1 = url1.find(':');
if (i1 != string::npos)
{
	protocol = url1.substr(0, i1 - 0);
	cout << protocol << endl;
}
// strchar
size_t i2 = url1.find('/', i1 + 3);
if (i2 != string::npos)
{
	domain = url1.substr(i1 + 3, i2 - (i1 + 3));
	cout << domain << endl;

	uri = url1.substr(i2 + 1);
	cout << uri << endl;
}

// strstr  10:40继续
size_t i3 = url1.find("baidu");
cout << i3 << endl;

std::string str("Please, replace the vowels in this sentence by asterisks.");
cout << str << endl;

find_first_of

// strtok
std::size_t found = str.find_first_not_of("aeiou");
while (found != std::string::npos)
{
	str[found] = '*';
	found = str.find_first_not_of("aeiou", found + 1);
}
cout << str << endl;

cout << (url1 < url2) << endl;

3.string类的模拟实现

3.1经典的string类问题

来模拟实现string类,最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数,以下模拟实现string类的框架代码.

// 为了和标准库区分,此处使用String
class String
{
public:
	/*String()
	:_str(new char[1])
	{*_str = '\0';}
	*/
	//String(const char* str = "\0") 错误示范
	//String(const char* str = nullptr) 错误示范
	String(const char* str = "")
	{
		// 构造String类对象时,如果传递nullptr指针,可以认为程序非
		if (nullptr == str)
		{
			assert(false);
			return;
		}
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}
	~String()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}
private:
	char* _str;
};
// 测试
void TestString()
{
	String s1("hello bit!!!");
	String s2(s1);
}

说明:

上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。

3.2 浅拷贝

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。

如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。

3.3 深拷贝

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。(即也就是要开辟一个相同大空间,将拷贝的空间和原本空间的属性数据都一样)

3.3.1 传统版写法的String类

class String
{
public:
	String(const char* str = "")
	{
		// 构造String类对象时,如果传递nullptr指针,可以认为程序非
		if (nullptr == str)
		{
			assert(false);
			return;
		}
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}
	String(const String& s)
		: _str(new char[strlen(s._str) + 1])
	{
		strcpy(_str, s._str);
	}
	String& operator=(const String& s)
	{
		if (this != &s)
		{
			char* pStr = new char[strlen(s._str) + 1];
			strcpy(pStr, s._str);
			delete[] _str;
			_str = pStr;
		}
		return *this;
	}
    ~String()
    {
	    if (_str)
	    {
		    delete[] _str;
		    _str = nullptr;
	    }
    }
    private:
	    char* _str;
};

3.3.2 现代版写法的String类

class String
{
public:
	String(const char* str = "")
	{
		if (nullptr == str)
		{
			assert(false);
			return;
		}
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}
	String(const String& s)
		: _str(nullptr)
	{
		String strTmp(s._str);
		swap(_str, strTmp._str);
	}
	// 对比下和上面的赋值那个实现比较好?
	String& operator=(String s)
	{
		swap(_str, s._str);
		return *this;
	}
	/*
	String& operator=(const String& s)
	{
	if(this != &s)
	{
	String strTmp(s);
	swap(_str, strTmp._str);
	}
	return *this;
}
*/
	~String()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}
private:
	char* _str;
};

3.3 写时拷贝(了解)

写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的
引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。(即把一个对象应用次数计数,从1开始,如果又几个函数或者对象要拷贝应用,每次计数++,如果对拷贝的对象不应用,则计数-1,若计数为1时不应用或者不要,则调用析构函数  ,释放对象的内存空间)

3.4 模拟实现

成员变量

private:
	char* _str;
	size_t _size;
	size_t _capacity;

public:
	const static size_t npos;//指的是常数的最大值
    

_str:表示字符串,_size:表示字符串的长度,_capacity:指的是当前的容量

获得成员变量值

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

//返回capacity
size_t capacity() const
{
	return _capacity;
}

迭代器

typedef char* iterator;
iterator begin()
{
	return _str;
}

iterator end()
{
	return _str + _size;
}

//const对象的迭代器
typedef char* const_iterator;

const_iterator begin() const
{
	return _str;
}

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

构造函数

//无参构造
/*string():
	_str(new char[1]),
	_size(0),
	_capacity(0)
{	
	_str[0] = '\0';
}*/

//有参构造函数
string(const char* str = "") :
	_size(strlen(str))
{
	_capacity = _size;
	_str = new char[_capacity + 1],
	strcpy(_str, str);
}

拷贝构造

//拷贝构造
//传统写法
//string(const string& s)
//{
//	//开空间
//	_str = new char[strlen(s._str) + 1];
//	strcpy(_str, s._str);
//	_size = s._size;
//	_capacity = s._capacity;
//}

//拷贝构造
//现代写法
string(const string& s)
{
	string tmp(s._str);
	swap(tmp);
}

析构函数

//析构函数
~string()
{
	delete[] _str;
	_str = nullptr;
	_size = _capacity = 0;
}

运算符重载

//由于_str开辟的空间是堆上的,即函数调用完还存在
//operator[]
char& operator[](size_t pos)
{
	//判断pos位置是否越界
	assert(pos<_size);

	return _str[pos];
}

//const成员重载operator[]
const char& operator[](size_t pos) const
{
	//判断pos位置是否越界
	assert(pos<_size);

	return _str[pos];
}

//operator+=
//返回string对象字符串
string& operator+=(char c)
{
	push_back(c);
	return *this;
}

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

//operator+
string operator+(const char* str)
{
	//开辟一个string空间
	string tmp;
	tmp += str;
	tmp += _str;
	return tmp;
}

赋值重载

//赋值重载
//operator=
//s1(s3) 两个对象都是已经有的
//string& operator=(string& s)
//{
//	//开辟和s一样大的空间
//	char* tmp = new char[strlen(s._str)];
//	strcpy(tmp, s._str);
//	delete[] _str;

//	_str = tmp;
//	_size = s._size;
//	_capacity = s._capacity;
//	return *this;
//}

//赋值重载
//现代写法
string& operator=(string tmp)
{
	// 现代写法
	swap(tmp);

	return *this;
}

比较重载运算符 

//operator==
bool operator==(const string& s1, const string& s2)
{
	int ret = strcmp(s1.c_str(), s2.c_str());
	return ret == 0;
}

//operator>
bool operator>(const string& s1, const string& s2)
{
	int ret = strcmp(s1.c_str(), s2.c_str());
	return ret > 0;
}

//operator>=
bool operator>=(const string& s1, const string& s2)
{
	return (s1==s2) && (s1>s2);
}

//operator<
bool operator<(const string& s1, const string& s2)
{
	return !(s1>=s2);
}

//operator<=
bool operator<=(const string& s1, const string& s2)
{
	return !(s1 > s2);
}

//operator!=
bool operator!=(const string& s1, const string& s2)
{
	return !(s1 == s2);
}

流插入运算符

//cout>>string对象
//流插入
//不用给与友元,迭代器将工作做完了,没有用到string对象的私有成员
ostream& operator<<(ostream &out,string& s)
{
	for (auto ch : s)
	{
		out << ch;
	}
	
	return out;
}

 

流提取运算符

//流提取
istream& operator>>(istream& in, string& s)
{
	//首先清理
	s.clear();
	char ch;
	
	//可以获得空格字符
	ch=in.get();
	char buff[128];
	size_t i = 0;
	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;
		//[0, 126]
		if (i == 127)
		{
			buff[127] = '\0';
			s += buff;
			i = 0;
		}
		ch=in.get();
	}

	if (i > 0)
	{
		buff[i] = '\0';
		s += buff;
	}

	return in;
}

getline:一次获得一行字符串,包括空格

//getline
istream& getline(istream& in, string& s)
{
	s.clear();

	//逐个字符输入
	char ch;
	ch = in.get();
	char buff[128];
	size_t i = 0;
	while (ch != '\n')
	{
		buff[i++] = ch;

		if (i == 127)
		{
			buff[127] = '\0';
			s += buff;
			i = 0;
		}
		ch=in.get();
	}

	if (i > 0)
	{
		buff[i] = '\0';
		s += buff;
	}
	return in;
}

reserve

 

//reserve保留内存
void reserve(size_t n = 0)
{
	if (n > _capacity)
	{
		char* tmp = new char[n+1];
		strcpy(tmp, _str);
		delete[] _str;
		// _str指向tmp这段空间
		_str = tmp;
		_capacity = n;
		
	}
}

提前开辟内存时,要先开辟一个n的空间,然后将_str内存值给与tmp,之后清空_str内存,之后再tmp赋值给_str.

push_back 

//尾插push_back
void push_back(char c)
{
	//判断扩容,内存不够,扩容2倍
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : 2 * _capacity);
	}

	//尾插
	_str[_size] = c;
	_size++;
	_str[_size] = '\0';
	
}

append

//附加字符串
void append(const char* str)
{
	//判断字符串的长度,扩容
	size_t len = strlen(str);
	if (_size + len >= _capacity)
	{
		reserve(_size + len);
	}

	//尾插字符串
	//strcat
	//char * strcat ( char * destination, const char * source );
	//从_str的_size位置开始追加str
	strcat(_str+_size, str);
	_size += len;
}

 事先开辟内存,之后用strcat追加

insert:在pos位置加入一个字符串

//在pos位置前插入字符
void insert(size_t pos, char ch)
{
	assert(pos <= _size);
	//判断扩容
	// 扩容2倍
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : 2 * _capacity);
	}

	//将元素向后移动1位,之后在pos位置插入
	/*size_t end = _size;

	while (end >= 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++;
}

由于时size_t类型,无符号类型,由于时索引,也要注意索引小于0 的情况

insert:在pos位置加入一个字符串

//在pos位置插入一个字符串
void insert(size_t pos, const char* str)
{
	assert(pos <= _size);

	//扩容
	size_t len = strlen(str);
	if (len + _size > _capacity)
	{
		reserve(len + _size);
	}

	//将pos位置之后的字符向后挪动len
	//防止是头插入时,无符号整数会减到0是会变得更大会导致死循环
	size_t end = _size + len;
	while (end > pos+len-1)
	{
		_str[end] = _str[end-len];
		--end;
	}

	//插入字符串
	strncpy(_str + pos, str, len);
	_size += len;
}

 erase:删除字符

//删除字符
void erase(size_t pos, size_t len = npos)
{
	assert(pos < _size);
	
	//删除
	//					len + pos >= _size	如果pos=npos会有溢出的风险
	if (len == npos || len >= _size-pos)
	{
		_str[pos] = '\0';
		_size = pos; 
	}
	else
	{
		//从_str的pos位置开始拷贝,将pos开始len长度的字符串拷贝
		strcpy(_str + pos, _str + pos + len);
		_size -= len;
		
	}
}

由于时size_t类型,无符号类型,由于时索引,也要注意索引小于0 的情况

获得与C语言兼容的字符串(char*)

//获得与c语言兼容的字符串
char* c_str() const
{
	return _str;
}

resize

//resize
void resize(size_t n,char ch='\0')
{
	if (n <= _size)
	{
		_str[n] = '\0';
		_size = n;
	}
	else
	{
		reserve(n);

		size_t end = _size;
		for (size_t i = 0; i < n; i++)
		{
			_str[i] = ch;
		}
		_str[n] = '\0';
		_size = n;
	}
}

find:从任意位置为起点开始查找

//查找字符的所在位置
//从任意位置为起点开始
size_t find(char c, size_t pos = 0) const
{
	assert(pos <= _size);

	//查找位置
	for (size_t i = pos; i < _size; i++)
	{
		if (_str[i] == c)
		{
			return i;
		}
	}
	//没有找到
	return -1;
}

find:从pos位置查找匹配的字符串位置

//从pos位置查找匹配的字符串位置
size_t find(const char* str, size_t pos = 0) const
{
	assert(pos <= _size);

	//查找位置
	//strstr 匹配字符串
	char*p = strstr(_str + pos, str);

	//是否找到
	if (p)
	{
		//地址相减会得到距离
		return p - _str;
	}
	else
	{
		return -1;
	}
}

 substr:复制子字符串,要求从指定位置开始,并具有指定的长度。如果没有指定长度超出了源字符串的长度,则子字符串将延续到源字符串的结尾

string substr(size_t pos = 0, size_t len = npos) const
{
	assert(pos <= _size);

	string sub;
	//判断是否得到pos位置后全部字符
	//	  len+pos>=_size会导致
	if (len >= _size - pos)
	{
		for (size_t i = pos; i < _size; i++)
		{
			sub += _str[i];
		}
	}
	else
	{
		for (size_t i = pos; i < pos + len; i++)
		{
			sub += _str[i];
		}
	}

	return sub;
}

 swap:类内交换

void swap(string &s)
{
	//函数体中调用库里的本来的swap函数
	std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}

swap:类外交换

void swap(string& s1, string& s2)
{
	s1.swap(s2);

}

clear:清空字符串

//清理
void clear()
{
	_str[0] = '\0';
	_size = _capacity = 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值