C++(string):串的使用和模拟实现

-----“人生的上半场打不好没关系,还有下半场”

这篇博客主要针对C++ STL中串(string)的功能使用,以及其中重要功能的模拟实现。


(1)什么是string?

C语言中,string就是字符串,是以'\0'结尾的一些字符的集合。

但字符串本身和库函数是分离,需要用户去底层管理字符串,对于"马大哈"的用户容易越界访问。

string功能:

①string构造:

(constructor)函数名称功能说明
string() 构造空的string类对象,即空字符串
string(const char* s) (重点)用C-string来构造string类对象
string(size_t n, char c)string类对象中包含n个字符c
string(const string&s)拷贝构造



②容量操作:

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

 rsize 和 reserve:

resize:

reserve:

 

	string s1("hello");
	s1.reserve(20);
	cout << s1 << endl;

	s1.resize(10, 'c');
	cout << s1 << endl;

 

 结论:单从结果上来看resize 与 reserve的差别在于,扩容后,是否增加新的字符(初始化)。


 ③字符串遍历:

1.用数组的方式[].

在string库中,重载来[]来访问的operator:

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

 2.迭代器:

    string s1("hello vscode");
   //指名 迭代器属于哪个类里面的
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		//同样也可以修改
		*it += 1;
		cout << *it <<" ";
		it++;
	}
	cout << endl;

 3.范围for:

 之后模拟实现会说说,范围for的原理。


 ④string类对象的修改:

刚刚呢,我们可以通过下标,对具体某个值的改变。

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

1.增加(尾增)

可以看出,使用operator可读性强,且就是append和push_back  的复用。 

2.查找:

  string str("file.cpp");
        //默认从(起始)地址处往后找 找到了就返回第一个下标位置
	size_t pos1 = str.find("cpp",3);
	cout << pos1 << endl;

	size_t pos2 = str.find("h", 0);
	cout << pos2 << endl;

 

      // npos是string里面的一个静态成员变量
	  static const size_t npos = -1;

 

 如果找不到,就直接返回npos,也就是-1:


string类非成员函数:

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

 ⑥其他类:

函数功能
insert在pos位置处插入字符
erase在pos~n位置删除字符

 insert:

在pos位置之前插入。 

erase:

当erase没有给任何值的时候,也就是全部删除。 


(2)string的模拟实现:

1.构造函数与析构;


//不好的写法string(const char* str="\0")  这里会有两个'\0'
string(const char* str="")
		{	
			_size = strlen(str);
			_capacity = _size;
			//多开一个空间放"\0"
			_str = new char[_capacity+ 1];
			//并把值拷贝过来
			strcpy(_str, str);
		}

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

字符串和空字符串:

 2.拷贝构造和赋值重载;

        //拷贝构造
		//s2(s1)
		string(const string& s)
			:_str(new char[strlen(s._str) + 1]),
			_size(s._size),
			_capacity(s._capacity)  //被拷贝构造的 类 直接给定原数
		{
			//再加上内容拷贝
			strcpy(_str, s._str);
		}


    	//赋值运算符重载
	string& operator=(const string& s)
		{
			//把自己的内存释放掉
			delete[] _str;

			//开和它一样大的空间
			_str = new char[strlen(s._str) + 1];
			strcpy(_str, s._str);

			return *this;
		}

 拷贝构造:

赋值运算重载: 

 上面两种是传统写法:

接下来,写一个现代写法:其思想就是交换。

现代拷贝构造和赋值重载:

代码简单。其跟本思想,就是去构造一个类,并把要做的事情让要构造的类,先和它做。 

我们看到两份代码,在swap的时候,有相同的步骤,所以可以进行合并如下:

   //此时需要注意 此刻的swap 和  string 的库函数swap同名,因为原则是就近 但需要使用全局函数(库函数)  所以需要加::让编译器去全局找函数
		void swap(string& tmp)
		{
			::swap(_str, tmp._str);
			::swap(_size, tmp._size);
			::swap(_capacity, tmp._capacity);
		}

		string(const string& s)
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{
			//创建一个tmp 并由s 构造
			string tmp(s._str);
			//this->swap(tmp)
			swap(tmp);
		/*	swap(_str,tmp._str);
			swap(_size, tmp._size);
			swap(_capacity, tmp._capacity);*/
		}

		//s1=s3
		string& operator=(const string& s)
		{
			string tmp(s._str);
			swap(tmp);
			/*swap(_str, tmp._str);
			swap(_size, tmp._size);
			swap(_capacity, tmp._capacity);*/

			return *this;
		}

3.string遍历:

operator [] 1.

//返回pos位置的值
char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}

可更改数值:


 operator [] 2.

//修饰返回值                       //修饰this 因为是const string
const char& operator[](size_t pos)const 
		{
			assert(pos < _size);
			return _str[pos];
		}

 无法改变值

 

迭代器:

string是类似于指针。

//首先重定义
typedef char* iterator

typedef char* iterator;

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

同样,也可以设置通过迭代器不可更改。

	typedef const char* const_itertor;


//返回值 和 this 参数  都用const修饰
const_itertor begin()const
		{
			return _str;
		}

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

注: 这里迭代器的形式和指针一样,仅仅是在string类这里是这样

 范围for:

范围for虽然很神奇,但其中的原理很简单。其依托的就是迭代器。

 当我们把迭代器的一侧屏蔽>_

for范围,在编译的过程中,会被编译器自动看成迭代器!!


4.增加与扩容:

扩容:

resize:扩容并初始化

//扩容
        //rsize  ---->不止扩容 还初始化
		void resize(size_t n,char val='\0')
		{
			//1.重设置 n<size
			if (n < _size)
			{
				_size = n;
				_str[_size] = '\0';
			}
			else
			{
				//2.不止补满capacity
				if (n > _capacity)
				{

				}
				//3.补在capacity以内
				for (int i = _size;i < n;++i)
				{
					//初始化
					_str[i] = val;
				}
				_str[n] = '\0';
				_size = n;
			}
		}

reserve:仅扩容

       void reserve(size_t n)
		{
			//为什么要判断? 解耦合
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				//不把'\0'拷贝过去
				strncpy(tmp, _str, _size);
				delete[] _str;

				_str = tmp;
				_capacity = n;
			}
		}

push_back:


	void push_back(char ch)
		{
			//增容
			if (_size == _capacity)
			{
				//开2倍
				reserve(_capacity==0?4:_capacity*2);
			}

			_str[_size] = ch;
			//不要忘了
			_str[_size + 1] = '\0';
			_size++;
		}

append:

void append(const char* str)
		{
			//增加字符串 可能>capacity 也可能<capacity
			size_t len = _size + strlen(str);
			if (len > _capacity)
			{
				reserve(len);
			}

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

operator+=:

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

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


5.插入(insert)、删除(erase)与查找(find):

insert(单个字符):

//在pos位置 之前插入
		string& insert(size_t pos, char ch)
		{
			//插入_size  就是尾插了
			assert(pos <= _size);
			if (_capacity == _size)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}

			//用下标
			size_t end = _size;
			while (end >=pos)
			{
				_str[end + 1] = _str[end];
				--end;
			}


			_str[pos] = ch;
			_size++;

			return *this;
		}

 可以发现当要头插,因为end为无符号整数,那么当end=-1时,会是一个很大的数导致程序崩死。

改良:

string& insert(size_t pos, char ch)
		{
			//插入_size  就是尾插了
			assert(pos <= _size);
			if (_capacity == _size)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}

			//用下标
			/*size_t end = _size;
			while (end >=pos)
			{
				_str[end + 1] = _str[end];
				--end;
			}*/

			//用指针 可以避免 size_t的较大值
			char* end = _str + _size;
			while (end >= _str + pos)
			{
				*(end + 1) = *end;
				--end;
			}

			_str[pos] = ch;
			_size++;

			return *this;
		}

 insert(字符串):

	string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			size_t len=strlen(str);
			if (len+_size > _capacity)
			{
				reserve(len + _size);
			}

			//挪动数据
			char* end = _str + _size;
			while (end >= _str + pos)
			{
				*(end + len) = *end;
				--end;
			}
			//拷贝多少字符就+多少
			strncpy(_str+pos,str, len);
			_size += len;

			return *this;
		}

 

 erase:

	static const size_t npos;
	const size_t string::npos = -1;


//注: npos是全局变量
		string& erase(size_t pos = 0, size_t len=npos)
		{
			assert(pos <= _size);
			//1.剩余字符小于 要删长度
			//2.剩余字符大于 要删长度
			size_t leftlen = _size - pos; //剩余字符
			if (len > leftlen)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				//把leftlen往前拷贝 //越过len+pos(要删除的字符)
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
			return *this;
		}

find:

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

 6.输入输出:

	//输出
	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			out << ch;
		}

		return out;
	}
	//输入
	istream& operator>>(istream& in,string& s)
	{
		//每次输入前先清空原有的地方
		s.clear();

		char ch;
		//获取字符
		ch = in.get();
		while (ch != ' ' && ch != '\n')
		{

			//利用增容机制
			s += ch;
			ch = in.get();
		}
		return in;
	}

getline:

istream& getline(istream& in, string& s)
	{
		//每次输入前先清空原有的地方
		s.clear();

		char ch;
		//获取字符
		ch = in.get();
		while ( ch != '\n')
		{

			//利用增容机制
			s += ch;
			ch = in.get();
		}
		return in;
	}

最后也就到这里 了,果然挺多了~ 

祝你好运~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值