C++基础篇之STL库(一)——string的使用及其模拟实现

string类简介

在C语言中,C标准库为了操作字符串(以’\0’结尾的一些字符的集合)更加方便,提供了一些str系列的库函数。
在C++中,则提供了更加高效与方便的String类,类成员函数更加全面,可以帮助我们对字符串进行增删查改等一系列操作。
在使用string类时,必须包含#include 以及using namespace std;

string类成员变量

模拟实现的string类主要包含三个成员变量:

  • 字符串首地址
  • 字符串有效字符个数
  • 字符串容量

因为字符串在内存地址空间是连续的,只需要首地址+下标便可以找到每个位置上的值;
字符串有效字符个数表示当前字符数组存储的字符个数(不包括‘\0’)
字符串容量表示为字符数组开辟的空间(不包括‘\0’)

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

string类构造与析构

构造函数

库函数
库函数中构造函数重点需要掌握的有三个:

函数名称功能说明
string()默认构造
string(const char* str)字符串构造
string(size_t n, char chn个字符构造

默认构造:

string s1;

字符串构造:

string s2("sherry");

n个字符构造:

string s3(10, 'x');

模拟实现
构造一个全缺省的构造函数,当实例化对象时,不传参则构造出空字符串;传参时,构造出指定字符类。初始化有效字符个数_size和容量_capacity为字符串长度(不包括’\0’),开辟一个_capacity+1的空间保存字符串,并将开辟的地址拷贝给字符串首地址_str

string(const char* str = "")
	:_size(strlen(str))
	, _capacity(_size)
{
	_str = new char[_capacity + 1];
	strcpy(_str, str);
}

构造一个n个字符的构造函数,通过memset函数按字节把内存空间设置为ch,最后还需在_size位置设置字符串结束标志’\0’。

string(size_t n,char ch)
	:_size(n)
	, _capacity(_size)
{
	_str = new char[_capacity + 1];
	memset(_str, ch, _size);
	_str[_size] = '\0';
}

拷贝构造

库函数
当用已存在string类初始化一个正在创建的string类,调用拷贝构造函数。

string s1("hello world");
string s2(s1);

模拟实现
如果string类调用默认的拷贝构造函数,只会进行浅拷贝,s1和s2会指向同一块地址空间,如果不调用析构函数,并不会出现问题,但只要调用析构函数,同一块内存空间被析构两次,程序崩溃。
在这里插入图片描述
浅拷贝也称位拷贝,只是讲对象的值拷贝过来,会导致多个对象共享一份资源,当一个对象销毁时,该资源就会被销毁掉,其他对象再调用这份资源时,就会发生访问越界的问题。
为了解决此类问题,我们需要显示给出构造函数,进行深拷贝。深拷贝是给每个对象分配独立的资源,保证每个对象之间不会因共享资源而造成多次释放引起的程序崩溃。

  • 传统写法
// 拷贝构造(深拷贝) 
string(const string& s)
	:_size(s._size)
	, _capacity(s._capacity)
{
	_str = new char[_capacity + 1];
	strcpy(_str, s._str);
}
  • 现代写法
string(const string& s)
	:_str(nullptr)
	, _size(0)
	, _capacity(0)
{
	string tmp(s._str);
	//std::swap(_str, tmp._str);
	//std::swap(_size, tmp._size);
	//std::swap(_capacity, tmp._capacity);
	swap(tmp);
}

现代写法调用构造函数,创建了一个临时对象tmp,交换tmp*this即可。

赋值重载

库函数

string s1("hello world");
string s3("sherry");
s1 = s3;
s3 = s3;

模拟实现

  • 传统写法
// 传统写法
string& operator=(const string& s)
{
	if (this != &s)
	{
		char* tmp = new char[s._capacity + 1];
		strcpy(tmp, s._str);
		delete[] _str;
		_str = tmp;
		_size = s._size;
		_capacity = s._capacity;
	}
	return *this;
}
  • 现代写法
string& operator=(const string& s)
{
	if (this != &s)
	{
		//string tmp(s);	// 调用拷贝构造
		string tmp(s._str); // 调用构造函数
		
		//std::swap(_str, tmp._str);
		//std::swap(_size, tmp._size);
		//std::swap(_capacity, tmp._capacity);
		swap(tmp);	
	}
	return *this;
}

在现代写法的基础上进一步改进,在传参的时候不传引用,便会调用拷贝构造初始化形参s,将s*this进行交换即可,出了作用域,形参s自动调用析构函数进行销毁。

string& operator=(string s)
{
	//std::swap(_str, s._str);
	//std::swap(_size, tmp._size);
	//std::swap(_capacity, tmp._capacity);
	swap(s);
	return *this;
}

析构函数

析构函数只需要把申请的空间销毁即可

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

string类迭代器

迭代器按功能分为:

  • iterator
  • const_iterator
  • reverse_iterator
  • const_reverse_iterator
typedef char* iterator;
typedef const char* const_iterator;
		
typedef reverse_iterator<const_iterator, const char&, const char*>const_reverse_iterator;
typedef reverse_iterator<iterator, char&, char*> reverse_iterator;

反向迭代器库函数实现:

int main()
{
	string s1("sherry");
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		*it -= 1;
		++it;
	}
	cout << endl;
	
	it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	
	// 反向迭代器
	string::reverse_iterator rit = s1.rbegin();
	while (rit != s1.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
}

模拟实现

iterator begin()
{
	return _str;
}

iterator end()
{
	return _str + _size;
}

const_iterator begin() const
{
	return _str;
}

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

string类容量操作

string类的容量改变使用reserve和resize函数。

reserve函数

在这里插入图片描述
reserve函数是为string预留n个字节空间,不改变有效元素(_size)的个数。
注意:当reserve的参数小于string底层空间大小时,reserve不会改变容量(_capacity)大小。
模拟实现

void reserve(size_t n)
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);
		delete[] _str;

		_str = tmp;
		_capacity = n;
	}
}

resize函数

在这里插入图片描述
std库中的resize函数有两种方式,第一种是将字符串中有效字符个数改变到n个,用0来填充多余的元素空间;第二种是用字符c来填充多余的元素空间。
注意:如果改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小(_capacity)是不变的。
模拟实现

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

string类插入

push_back

pusk_back尾插函数,每次向string类字符串尾插一个字符。

string s1;
s1.push_back('x');
s1.push_back('y');
s1.push_back('z');

模拟实现

void push_back(char ch)
{
	if (_size == _capacity)
	{
		// 增容
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	_str[_size] = ch;
	++_size;
	_str[_size] = '\0';
	
	//insert(_size, ch);
}

append

append函数可以在string类字符串后增加字符串。

string s1("hello");
s1.append(" world");

模拟实现

void append(const char* str)
{
	// 增容
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}

	strcpy(_str + _size, str);
	_size += len;

	//insert(_size, str);
}

+=

string中重载了+= 运算符,因为+=更加人性化。
+=函数服用了push_back和append函数。

string s1;
s1 += 'x';
s1 += " ";
s1 += "yyy";

模拟实现

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

insert

在这里插入图片描述

string s1("hello");
s1.insert(0, 1, ' ');
s1.insert(6, "world");

模拟实现

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;

	return *this;
}

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 = len + _size;
	while(end >= pos + len)
	{
		_str[end] = _str[end - len];
		--end;
	}
	strncpy(_str + pos, str, len);
	_size += len;

	return *this;
}

string类删除

在这里插入图片描述
第一种erase()函数有两个参数,第一个是删除位置,第二个是删除长度,缺省值代表将后面的字符串全部删除;第二种erase()函数的参数是迭代器,可以传入迭代器,删除当前迭代器位置的字符;

string s2("hello world");
s2.erase(0, 1);
s2.erase(s2.begin());

删除分为两种情况,第二种删除当前位置及其之后的所有字符,这时只需要把pos位置置为’\0’即可;第二种删除某一段字符,这时候涉及到字符的挪动。
模拟实现

string& erase(size_t pos = 0, size_t len = npos)
{
	assert(pos < _size);
	if (len == npos || pos + len > _size)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		strcpy(_str + pos, _str + pos + len);
		_size -= len;
	}
	return *this;
}

string类查找

string类的查找函数find,可以查找一个字符,也可以在指定位置开始查找字符串。

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

模拟实现

size_t find(char ch)
{
	for (size_t i = 0; i < _size; i++)
	{
		if (ch == _str[i])
		{
			return i;
		}
	}
	return npos;
}

size_t find(const char* str, size_t pos = 0)
{
	const char* ptr = strstr(_str + pos, str);
	if (ptr == nullptr)
	{
		return npos;
	}
	else
	{
		return ptr - _str;
	}
}

[]运算符重载

string类最方便的在于可以随机访问,而可以想数组一样随机访问字符串中的任意位置,是因为重载了[]运算符。
模拟实现

char& operator[](size_t pos)
{
	assert(pos < _size);
	return _str[pos];
}

const char& operator[](size_t pos) const
{
	assert(pos < _size);
	return _str[pos];
}

流插入<<和流提取>>运算符重载

为了实现能够对string类的流插入和流提取,我们在string类外定义流插入和流提取函数。如果在类内实现,类的成员函数第一个参数默认是this,就只能实现成s1 >> cin(s1 << cout),不符合我们的使用习惯。

// 流插入流提取
ostream& operator<<(ostream& out,const string& s)
{
	//for (auto ch : s)
	//{
	//	out << ch;
	//}
	for (size_t i = 0; i < s.size(); ++i)
	{
		out << s[i];
	}
	// 不能使用out << s.c_str();
	return out;
}

istream& operator>>(istream& in, string& s)
{
	s.clear();
	char ch = in.get();
	while (ch != '\n' && ch != ' ')
	{
		s += ch;
		ch = in.get();
	}
	return in;
}

其他函数

// 类成员函数
const char* c_str() const
{
	return _str;
}

size_t size() const
{
	return _size;
}

// 类外的函数
bool operator==(const string& s1, const string& s2)
{
	return strcmp(s1.c_str(),s2.c_str()) == 0;
}

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

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

bool operator>=(const string& s1, const string& s2)
{
	return !(s1 < s2);
}
  • 37
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 25
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值