【C++】STL之string功能及模拟实现

目录

前沿

一、标准库中的string类

二、string类的常用接口说明

 1、string类对象的常见构造

 2、string类对象的容量操作

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

 4、string类对象的修改操作

 5、string类非成员函数

 6、vs下string结构的说明

三、string类的模拟实现

 1、构造函数

 2、析构函数

 3、拷贝构造函数

 4、赋值运算符重载

 5、比较运算符重载

 6、push_back ,append,+=

 7、容量(resize,reserve)(重点)

 8、插入和删除(insert,erase)

 9、迭代器


前沿

STL (standard template libaray-标准模板库)是 C++ 标准库的重要组成部分,由六大部分构成:仿函数,空间配置器,算法,容器,迭代器和配接器,其中包含了各式各样的容器,方便我们以后编写程序,比如今天要总结的 string 容器。

一、标准库中的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 string。
  4. 不能操作多字节或者变长字符的序列。

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

二、string类的常用接口说明

 1、string类对象的常见构造

(constructor)函数名称 功能说明
string()(重点) 构造一个空字符串,长度为0个字符。
string (const char* s)(重点) 通过C字符串构造string类对象
string (const string& str)(重点) 拷贝构造
string (const string& str, size_t pos, size_t len = npos) 复制str从pos位置开始往后npos个字符
string (const char* s, size_t n) 复制C字符串前n个字符
string (size_t n, char c) string类对象包含n个字符c
template < class InputIterator > string (InputIterator first, InputIterator last) 通过一个字符串区间构造对象
void Teststring()
{
	string s1;              // 构造空的string类对象s1
	string s2("hello bit"); // 用C格式字符串构造string类对象s2
	string s3(s2);          // 拷贝构造s3
}

 2、string类对象的容量操作

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

注意:

  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不会改变容量大小。
#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1("hello world");
	s1.reserve(10);
	s1.resize(6, '0');
	return 0;
}

当运行到 reserve 时:

我们发现当参数小于size时,改操作对参数几乎没有影响。

当运行resize时:

我们发现这里参数 size 发生了改变,但是没有改变容量的大小。

注意:当 reserve 参数大于 size 时就会开出大于参数的空间,具体是多少由编译器决定,resize 的第一个参数大于 size 时也会扩大空间,同时还将 size 扩大到 n,在原始数据上将后面数据初始化为ch.

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

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

 (1)可以通过[]+下标的方式来获取该字符串某个位置的字符。

void Test()
{
	string s1("hello World!");
	cout << s1[0] << " " << s2[0] << endl;

	s1[0] = 'H';
	cout << s1 << endl;
}
int main()
{
	Test();
	return 0;
}

运行结果:

(2)还可以用迭代器的方式访问。

void Test()
{
	string s("hello world!");
	//begin代表字符串第一个位置
	string::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it;
		++it;
	}
	cout << endl;
	// string::reverse_iterator rit = s.rbegin();
	// C++11之后,直接使用auto定义迭代器,让编译器推到迭代器的类型
	auto rit = s.rbegin();
	while (rit != s.rend())
	{
		cout << *rit;
		rit++;
	}
	cout << endl;
}
int main()
{
	Test();
	return 0;
}

运行结果:

(3) 还可以用范围 for 的方式访问。

void Test()
{
	string s("hello world!");
	for (auto ch : s)
		cout << ch;
	cout << endl;
}
int main()
{
	Test();
	return 0;
}

运行结果:

 4、string类对象的修改操作

函数名称 功能说明
push_back 在字符串后尾插字符c
append 在字符串后追加一个字符串
operator+= 在字符串后追加字符串str
c_str 返回C格式字符串
find + npos 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
rfind 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
substr 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
insert 在源字符串某一位置插入字符串str
erase 在某一位置开始进行删除
swap 字符串交换
find_first_of 搜索字符串中与参数中指定的任何字符匹配的第一个字符
find_last_of 在字符串中搜索与参数中指定的任何字符匹配的最后一个字符
find_first_not_of 搜索字符串中与参数中指定的任何字符不匹配的第一个字符
find_last_not_of 在字符串中搜索不匹配其参数中指定的任何字符的最后一个字符
void Test()
{
	string str;
	str.push_back(' ');   // 在str后插入空格
	str.append("hello");  // 在str后追加一个字符"hello"
	str += 'w';           // 在str后追加一个字符'w'   
	str += "orld";          // 在str后追加一个字符串"orld"
	cout << str << endl;
	cout << str.c_str() << endl;   // 以C语言的方式打印字符串

	string s("world");
	s.insert(0, "hello");
	cout << s << endl;


	s.insert(2, "abcdef", 3);//插入该字符串的三个字符在二号位置
	cout << s << endl;

	string s("abcd");
	s.insert(s.find('b'), "abcdef");//找到字符b的位置然后插入"abcdef"
	cout << s << endl;

	s.replace(s.find('f'), 1, "20%");//找到f的位置用20%代替
	cout << s << endl;

	string s("hello world");
	size_t pos = s.find_first_of("eo", 0);//从位置0处找到字符e或字符o第一次出现的位置
	while (pos != string::npos)//如果没有找到匹配项,函数返回string::npos。
	{
		s[pos] = '*';
		pos = s.find_first_of("eo", pos + 1);//将整个字符串中的字符e或字符o换成*
	}

	//将不是e或o的字符都替换成*
	string s("hello world");
	size_t pos = s.find_first_not_of("eo", 0);
	while (pos != string::npos)
	{
		s[pos] = '*';
		pos = s.find_first_not_of("eo", pos + 1);
	}
	cout << s << endl;

	string s("hello world");
	size_t pos = s.find_last_of("eo", s.size() - 1);//从后开始找
	while (pos != string::npos)
	{
		s[pos] = '*';
		pos = s.find_last_of("eo", pos - 1);
	}
	cout << s << endl;
}

注意:

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

 5、string类非成员函数

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

   这里面的 getline 函数是获取一行字符串的,和单纯的流插入是不一样的,因为字符串中很可能会包含空格的,而 cin 会认为这是结束的标志,进而不再读取字符,因此才有了这个 getline 函数。

具体用法如下:

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s;
	getline(cin, s);
	cout << s << endl;
	return 0;
}

运行结果: 

 6、vs下string结构的说明

 注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。

   关于string对象应该占多少字节这个问题,我们先大胆猜一下为12个字节(一个指向字符串的指针,一个是整形的 size,一个整形的 capacity),但明显不是这样的,string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义 string 中字符串的存储空间:
  (1)当字符串长度小于16时,使用内部固定的字符数组来存放;
  (2)当字符串长度大于等于16时,从堆上开辟空间。

union _Bxty
{ 
    // storage for small buffer or pointer to larger one
	value_type _Buf[_BUF_SIZE];
	pointer _Ptr;
    // to permit aliasing
	char _Alias[_BUF_SIZE]; 
}_Bx;

   因为大多数情况下字符串的长度都小于16,那 string 对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高;还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量;还有一个指针做一些其他事情。因此总共占16+4+4+4=28个字节

三、string类的模拟实现

 1、构造函数


class string
{
public:
	string(const char* str = "")
		:_size(strlen(str))
	{
		_capcity = _size == 0 ? 4 : _size;
		_str = new char[_capcity + 1];
		strcpy(_str, str);
	}
private:
	char* _str;
	size_t _size;
	size_t _capcity;
};

 2、析构函数

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

 3、 拷贝构造函数

//传统版写法
string(const string& s)
	:_size(s._size)
	, _capcity(s._capcity)
{
	_str = new char[_capcity + 1];
	strcmp(_str, s._str);
}

//现代版写法
String(const string& s)
	:_str(nullptr)
{
	string str(s._str);
	swap(_str, str);
}

注意:第二种现代版写法, 一定要先构造一份对象然后再交换,要不然直接交换的话就把传过来的对象变空了。

 4、 赋值运算符重载

//传统版写法
string& operator=(const string& s)
{
	_size = s._size;
	_capcity = s._capcity;
    // 防止new失败,我们先创建一个临时变量开辟空间
	// 拷贝完后,在给_str
	char* tmp = new char[_capcity + 1];
	strcpy(tmp, s._str);
	delete[] _str;
	_str = tmp;
	return *this;
}

//现代版写法
String& operator=(String s)
{
	swap(_str, s._str);
	return *this;
}

 5、比较运算符重载

bool operator==(const string& s)
{
	return strcmp(_str, s._str) == 0;
}

bool operator>(const string& s)
{
	return strcmp(_str, s._str) > 0;
}

bool operator>=(const string& s)
{
	return *this > s || *this == s;
}

bool operator<(const string& s)
{
	return !(*this >= s);
}

bool operator<=(const string& s)
{
	return *this < s && *this == s;
}

bool operator!=(const string& s)
{
	return !(*this == s);
}

比较运算符的重载可以先写一个大于和等于的函数,其他的比较可以直接用这两个进行复用。 

 6、push_back ,append,+=

//增加一个字符
void push_back(char c)
{
	if (_size == _capacity)
	{
		reserve(_capacity * 2);
	}
	_str[_size++] = c;
	_str[_size] = '\0';
}
//增加一个字符串
void append(const char* str)
{
	int len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}
	strcpy(_str + _size, str);
	_size += len;
}

string& operator+=(char c)
{
	push_back(c);
	return *this;
}

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

 7、容量(resize,reserve)(重点)

void resize(size_t n, char c = '\0')
{
	if (n > _size)
	{
		if (n > _capacity)
		{
			reserve(n);
		}
		memset(_str + _size, c, n - _size);
	}
	_size = n;
	_str[n] = '\0';
}
void reserve(size_t n)
{
	if (n > _capcity)
	{
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;
		_capcity = n;
	}
}

 8、插入和删除(insert,erase)

//插入字符
string& insert(size_t pos, char ch)
{
	assert(pos <= _size);
	//插入前检查是否需要扩容
	if (_size + 1 > _capcity)
		reserve(_size * 2);
	//挪动数据
	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		--end;
	}
	_str[pos] = ch;
	_size++;
	return *this;
}
//插入字符串
string& insert(size_t pos, const char* s)
{
	assert(pos <= _size);
	size_t lens = strlen(s);
	if (_size + lens > _capcity)
		reserve(_size + lens);
	//挪动数据
	size_t end = _size + lens;
	while (end > pos + lens - 1)
	{
		_str[end] = _str[end - lens];
		--end;
	}
	strncpy(_str, s, lens);
	_size += lens;
	return *this;
}
string& erase(size_t pos, size_t len = npos)
{
	assert(pos < _size);
	//挪动数据
	if (len == npos || _size - pos <= len)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		size_t begin = pos + len;
		while (begin <= _size)
		{
			_str[begin - len] = _str[begin];
			++begin;
		}
		_size -= len;
	}
	return *this;
}

 9、迭代器

typedef char* iterator;
typedef const char* const_iterator;

iterator begin()
{
	return _str;
}
iterator end()
{
	return _str + _size;
}
const iterator begin()const
{
	return _str;
}
const iterator end()const
{
	return _str + _size;
}


本文要是有不足的地方,欢迎大家在下面评论,我会在第一时间更正。

老铁们,记着点赞加关注!!! 

猜你喜欢

转载自blog.csdn.net/m0_63198468/article/details/131616759