STL之string的使用与模拟实现

SQL之string的使用与模拟实现

  • 1.官方库中string类的使用接口
    • 1.1 工具网站搜索string类的使用
    • 2.2string类的常用接口说明
  • 2.模拟实现重要/常用的成员函数接口
    • 2.1 准备工作
      • 2.1.1. 解决命名冲突
      • 2.1.2. 成员变量
      • 2.1.3. 默认成员函数——构造函数/拷贝构造函数/析构函数
      • 2.1.4赋值运算符重载
    • 2.2成员函数模拟模拟实现
      • 2.2.1. string类对象的容量操作
      • 2.2.2. string类对象的访问及遍历操作
      • 2.2.3. string类对象的修改操作
      • 2.2.4. string类非成员函数——流插入,流输出
      • 2.2.5 swap交换

在这里插入图片描述

所属专栏:C“嘎嘎" 系统学习❤️
🚀 >博主首页:初阳785❤️
🚀 >代码托管:chuyang785❤️
🚀 >感谢大家的支持,您的点赞和关注是对我最大的支持!!!❤️
🚀 >博主也会更加的努力,创作出更优质的博文!!❤️

1.官方库中string类的使用接口

1.1 工具网站搜索string类的使用

  • 这里提供个大家一个查询官方库中各个函数的使用方法的网站链接: https://cplusplus.com/
    打开后是这个界面:
    在这里插入图片描述
  • 然后点击红色框框——切换老版本,因为新版本不支持查询搜索,老版本支持:
    在这里插入图片描述
  • 我们这里可以进行搜索string。
    在这里插入图片描述
    这里就会显示所有的string类中的成员函数,也可以点击进行详细的功能显示。

2.2string类的常用接口说明

  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)(重点) 拷贝构造函数
int main()
{

	string s1;//这种写法就等于string s1()这种写法,但是不能这样写,
	//因为编译器可能会把不能分辨他是一个函数还是一个对象
	string s2("hello world");
	string s3(4, 'a');
	string s4(s2);
	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;
	return 0;
}

在这里插入图片描述

  1. 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不会改变容量大小
int main()
{
	string s1("hello world");
	cout << "size:" << s1.size() << endl;//这个等同于length
	cout << "capacity:" << s1.capacity() << endl;

	s1.resize(13);
	cout << "size:" << s1.size() << endl;
	cout << "capacity:" << s1.capacity() << endl;

	s1.reserve(20);
	cout << "size:" << s1.size() << endl;//这个等同于length
	cout << "capacity:" << s1.capacity() << endl;

	s1.clear();
	if (s1.empty())
	{
		cout << "内容清空" << endl;
	}
	return 0;
}

在这里插入图片描述

  1. string类对象的访问及遍历操作
函数名称功能说明
operator[] (重点)返回pos位置的字符,const string类对象调用
begin+ endbegin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
rbegin + rendrbegin获取最后一个字符下一个位置的迭代器 + rend获取第一个字符位置的迭代器
范围forC++11支持更简洁的范围for的新遍历方式
int main()
{
	string s1("hello world");

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

	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it;
		it++;
	}
	cout << endl;

	string::reverse_iterator ri = s1.rbegin();
	while (ri != s1.rend())
	{
		cout << *ri;
		ri++;
	}
	cout << endl;
	return 0;
}

在这里插入图片描述

  1. string类对象的修改操作
函数名称功能说明
push_back在字符串后尾插字符c
append在字符串后追加一个字符串
operator+= (重点)在字符串后追加字符串str
c_str(重点)返回C格式字符串
find + npos(重点)从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
rfind从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
substr在str中从pos位置开始,截取n个字符,然后将其返回
int main()
{
	string s1("hello world");
	
	cout << s1.c_str() << endl;
	
	s1.push_back('!');
	cout << s1 << endl;

	s1.append(" I am user");
	cout << s1 << endl;

	s1 += " !";
	cout << s1 << endl;

	int pos = s1.find('!');
	cout << pos << endl;

	pos = s1.rfind('!');
	cout << pos << endl;
	return 0;
}

在这里插入图片描述

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

这里说明一下getline,其他的在模拟实现里讲解。
我们正常输入一个字符串的时候大家可能都会想先想到cin输入流,但是cin遇到空格或者换行时停止输入。
所以如果我们要输入一个字符串,包含空格的化就要用到getline。

int main()
{
	string s1;

	getline(cin, s1);
	cout << s1 << endl;
	return 0;
}

2.模拟实现重要/常用的成员函数接口

2.1 准备工作

2.1.1. 解决命名冲突

首先要模拟string类,我们就要创建一个类,但是如果我们直接创建一个string类的话,会和库里面的string类相互冲突,于是就要用到我们前面的知识点——命名空间。我们可以把我们自己写的string类写在我们自己定义的一个命名空间里面,这样就避免了冲突。

namespace qfw
{
	class string
	{
		//……
	};
}

2.1.2. 成员变量

要模拟实现一个sting类,首要的是要弄明白string类的内部结构,最重要的就是它的成员变量有哪些。从上面的 使用情况来看,我们可以判断首先有存放字符串的一个指针_str,再就是string的容量_capacity,以及string的字符个数_size。这我们也可以结合之前的数据结构中的单链表来类比。

   private:
       char* _str = nullptr;
       size_t _capacity = 0;
       size_t _size = 0;
       const static size_t npos = -1;//全部变量,用来部分成员函数的默认参数

2.1.3. 默认成员函数——构造函数/拷贝构造函数/析构函数

  • 构造函数的设计
    上面的使用情况,我们可以知道,构造函数有四种情况1.空构造,2.字符串构造,3.n个字符构造,4.拷贝构造
    这里我们可以将其总结成两大类别:1.空构造,2.有参构造。
    所以这里要设计成缺省参数。
       string(const char* str = "")
       {
           _size = strlen(str);
           _capacity = _size;
           _str = new char[_capacity + 1];//这里多开一个空间给"\0"
           strcpy(_str, str);
       }

这里能不能吧默认参数写成const char* str = nullptr,答案是不能,因为如果时空构造的话,str=nullptr这个时候strlen()计算大小的时候就相当于对str进行解引用,也就是对nullptr进行解引用了。

  • 拷贝构造
    如果这里我们不自己写拷贝构造的话,编译器就会调用默认拷贝构造,就是浅拷贝。但是这里我们成员变量中有指针,而且这个指针是后续用来动态开辟内存的,也就是说涉及到了动态的资源管理。这个问题我们之前的章节也有讲过,如果是用浅拷贝的话就会出现野指针的问题,也就是所同一块空间被释放了两次,所以这里就不能使用浅拷贝,而是要用深拷贝。
     string(const string& s)
     {
         _str = new char[s._capacity + 1];//这里多开一个空间给"\0"
         _size = s._size;
         _capacity = s._capacity;
         strcpy(_str, s._str);
     }

这里也有第二种写法:

string(const string& s)
{
	string tmp(s._str);
	swap(tmp);
}

注:swap函数后面模拟实现中有写到。
这种写法巧妙的服用了默认构造函数。
在这里插入图片描述

  • 析构函数
    析构函数就像对于比较简单,直接释放就行。
      ~string()
      {
          delete[] _str;
          _str = nullptr;
          _size = 0;
          _capacity = 0;
      }

2.1.4赋值运算符重载

string& operator=(const string& s)
{
	char* tmp = new char[s._capacity + 1];//先开辟一块新的空间
	delete[] _str;//释放原来的空间
	_str = tmp;//指向新的空间
	strcpy(_str, s._str);//将值拷贝到新的空间
	_size = s._size;
	_capacity = s._capacity;
	return *this;
}

这里也有第二种写法:

	string& operator=(string s)//注意这里是传值操作,并且不能别const修饰
	{
		swap(s);
		return *this;
	}

这里同样巧妙服用了拷贝构造。
在这里插入图片描述

2.2成员函数模拟模拟实现

2.2.1. string类对象的容量操作

	size_t size()
	{
		return _size;
	}

	size_t capacity()
	{
		return _capacity;
	}

    bool empty()const
    {
        return _size == 0;
    }

	void clear()
	{
		_str[0] = '\0';
		_size = 0;
	}
	void reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];//这里多开一个空间给"\0"
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}

   void  resize(size_t n, char c = '\0)
    {
        if (n > _capacity)
        {
            reserve(n);
        }

        if (n <= _size)
        {
            _str[n] = '\0';
            _size = n;
        }
        else
        {
            int cout = n - _size;
            for (int i = _size; i < n; i++)
            {
                _str[i] = c;
                _size++;
            }
            _str[_size] = '\0';
        }
    }

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

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;
}
const char& operator[](size_t pos) const//引用返回的两个好处:1.减少拷贝。2.修改返回值 //只读
{
	assert(pos <= _size);
	return _str[pos];
}

char& operator[](size_t pos)//引用返回的两个好处:1.减少拷贝。2.修改返回值 //可读可写
{
	assert(pos <= _size);
	return _str[pos];
}

2.2.3. string类对象的修改操作

void push_back(char ch)
{
	if (_size == _capacity)
	{
		size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(newCapacity);
	}
	_str[_size] = ch;
	_size++;
	_str[_size] = '\0';
}

void append(const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(len + _size);
	}
	strcpy(_str + _size, str);
	_size += len;
}

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

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

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

size_t find(char ch, size_t pos = 0) const
{
	assert(pos < _size);

	for (int i = pos; i < _size; i++)
	{
		if (_str[i] == ch)
			return i;
	}
	return npos;
}

size_t find(const char* str, size_t pos = 0) const
{
	assert(pos < _size);
	char* ret = strstr(_str, str);
	if (ret)
	{
		return ret - _str;
	}
	return npos;
}

string substr(size_t pos = 0, size_t len = npos) const
{
	string tmp;
	assert(pos < _size);
	size_t end = pos + len;
	if (len == npos || pos + len >= _size)
	{
		end = _size;
	}
	tmp.reserve(end - pos);
	for (int i = pos; i < end; i++)
	{
		tmp += _str[i];
	}
	return tmp;
}
void insert(size_t pos, char ch)
{
	assert(pos <= _size);
	if (_size == _capacity)
	{
		size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(newCapacity);
	}

	size_t end = _size + 1;
	while (end != pos)
	{
		_str[end] = _str[end - 1];
		--end;
	}
	_str[end] = ch;
	_size++;
	//return *this;
}

void 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 - 1))
	{
		_str[end] = _str[end - len];
		end--;
	}
	_size += len;
	strncpy(_str + pos, str, len);
	//return *this;
}

void 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;
}

2.2.4. string类非成员函数——流插入,流输出

ostream& operator<<(ostream& out, const string& s)
{
	for (int i = 0; i < s.size(); i++)
	{
		out << s[i];
	}
	return out;
}

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

2.2.5 swap交换

void swap(string& s)
{
	std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}
  • 27
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

初阳hacker

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值