[c++基础]-string类

本文详细讲解了C++ string类的学习理由、构造函数、容量操作、访问遍历、修改方法,以及非成员函数,并通过模拟实现加深理解。重点介绍了常用构造函数、字符串长度、容量控制、修改操作和相关函数的使用场景。
摘要由CSDN通过智能技术生成

前言

作者:小蜗牛向前冲

名言:我可以接受失败,但我不能接受放弃

 如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正。

目录

一、我们为什么要学习string 

二、怎么样去学习string 

1、string类常见的构造函数

2、string类对象的容量操作

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

4、string类对象的修改操作

5、 string类非成员函数

三、对string进行模拟


一、我们为什么要学习string 

我们都知道C语言为我们提供了许多有关字符串的库函数,但是这些都不符合C++类的模式,于是C++的官方就为我们通过了string类,方便我们解决有关字符串的问题。

二、怎么样去学习string 

下面我将带领大家去认识sring类中的成员函数及其相关知识。

1、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) (重点)

拷贝构造函数

下面我们在代码中理解一下:

//string 的构造函数
void test1()
{
	string s1;//构造空的string类对象,即空字符串
	string s2("hello world");//用C-string来构造string类对
	string s3(s2);//拷贝构造s3
	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
}

 这里我们可以发现s1对象由于我们没有传参所以,被初始化为空字符。

2、string类对象的容量操作

其实对于string类中存放字符的数据结构,我们可以理解为是一个顺序表,在下面模拟实现的时候将会得以体现。

函数名称

功能说明

size(重点)

返回字符串有效字符长度

length

返回字符串有效字符长度

capacity

返回空间总大小

empty (重点)

检测字符串释放为空串,是返回true,否则返回false

clear (重点)

清空有效字符

reserve (重点)

为字符串预留空间

resize (重点)

将有效字符的个数该成n个,多出的空间用字符c填充

 代码理解:

//string的容量
void test2()
{
	string s1("hello world");
	cout << s1.size() << endl;//字符有效长度
	cout << s1.length() << endl;//字符有效长度
	cout << s1.capacity() << endl;//返回空间总大小
	cout << s1.empty() << endl;//检测字符串释放为空串,是返回true,否则返回false
	string s2;
	s2.reserve(20);//为字符串预留空间
	cout << s2.capacity() << endl;
	s1.resize(20, '*');
	cout << s1 << endl;
}

 看到上面那么多容量接口,对于有些接口我们不免有一些疑问,比如reserve这个提前开空间有什么用,我们让他们自己进行扩容不可以吗?但是我们利用reserve提高插入数据的效率,避免增容带来的开销,所以说reserve是有必要存在的。

 注意: 

  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不会改变容量大小。

 

这里建议大家,如果遇到不明白的函数,其实我们是可以是相关的C++网站是读文档的,这样有利于培养我们对c++知识学习能力的自学能力。(博主一般在cpluspius网站上进行相关资料的查找)。

我举个例子:当我们不明白resize这的是干嘛的,我们就可以在网站是输入string 类,在找到这个类的容量文档(capacity)找到resize就可以查找了。

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

函数名称

功能说明

operator[] (重 点)

返回pos位置的字符,const string类对象调用

begin+ end

begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭 代器

rbegin + rend

begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器

范围for

C++11支持更简洁的范围for的新遍历方式

下面我将写出三种遍历方式来理解在string类中遍历方式:

void test3()
{
	string str1("1234");
	//遍历的三种方式

	//1 .下标遍历
	for (size_t i = 0;i < str1.size();++i)
	{
		//重载等价于:str1.operator[](i)+1
		str1[i]++;
	}
	cout << str1 << endl;

	//2.范围for
	for (auto& ch : str1)
	{
		ch--;
	}
	cout << str1 << endl;

	//反转
	size_t begin = 0,end = str1.size() - 1;
	while (begin < end)
	{
		swap(str1[begin++], str1[end--]);
	}
	//cout << str1 << endl;
	//3.迭代器 --通过访问的形式
	//string::iterator it1 = str1.begin();
	auto it1 = str1.begin();
	while (it1 != str1.end())
	{
		*it1 += 1;
		++it1;
	}
	it1 = str1.begin();
	while (it1 != str1.end())
	{
		cout << *it1 << " ";
		++it1;
	}
	cout << endl;
}

4、string类对象的修改操作

这里我们要借助string的成员函数完成对字符串的修改:

函数名称

功能说明

push_back

在字符串后尾插字符c

append

在字符串后追加一个字符串

operator+= (重点)

在字符串后追加字符串str

c_str(重点)

返回C格式字符串

find + npos(重点)

从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置

rfind

从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置

substr

在str中从pos位置开始,截取n个字符,然后将其返回

我们在对string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般 情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。

下面演示一下:

//验证+=
void test4()
{
	string s("hello");
	cout << s << endl;
	s += " world";
	cout << s << endl;
}

 append和push back都可以达到类似的效果,但是我们通常习惯用+=。

对于c str这个成员函数其实就是返回字符串首地址(也就是数组的首地址)

而find就是在字符串中找到某个字符的在顺序表(认为字符串是用顺序表存放的)中的下标:

5、 string类非成员函数

 这些函数虽然不是string的成员函数,但是都是和字符串相关的,我们在OJ刷题的时候可能会用到,这里我们也要认识一下!

函数

功能说明

operator+

尽量少用,因为传值返回,导致深拷贝效率低

operator>> (重点)

输入运算符重载

operator<< (重点)

输出运算符重载

getline (重点)

获取一行字符串

relational operators (重点)

大小比较

对于重载+其实他的本质就是调用拷贝构造:

//测试非string的成员函数
void test5()
{
	string s1("hello");
	string s2(" world");
	cout << s1 + s2 << endl;
}

这里的string的非成员函数还重载了流入和流提取 ,不是库中有吗?为什么我们还要重载呢?

这是因为库中的实现的并没有针对性,实现的可能不符合我们要输入或者输出自定义类型,所以我们可能重载出我们符合自己要求的流插入和流提取。(在下面我们会模拟实现<<和>>大家可以细细体会)

对于getline这有什么用了,下面我们来看到一个OJ题

#include <iostream>
using namespace std;

int main()
{
    string line;
    while(getline(cin,line))
    {
        size_t pos = line.rfind(' ');
        cout<<line.size()-pos-1<<endl;
    }
    return 0;
}

这里我们可以看到,我们并没有用流提取来获取字符串,这是因为流提取默认提取到空格或者换行就结束了,而对于一个字符串中可能存在空格,这样的话就不能用>>而要用getline或取一行字符。

好了对库中的string我们就先认识到这里,其他的还需要我们学会自己去查文档。

三、对string进行模拟

模拟实现中有许多细节,大家要细细观看噢!

#pragma once
#include<string.h>
#include<assert.h>
#include<istream>
#include<ostream>
using namespace std;
namespace pjb
{
	class string
	{
		typedef char* iterator;//重命名字符指针
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
	public:
		//这里要注意""这里是个字符串常量,'\0'是一个字符常量,\0是个整形常量
		//构造函数
		string(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];//多开一个空间放'\0'
		}
		//拷贝构造s2(s1)
		string(const string& s)
		{
			_str = new char[s._capacity + 1];//为s2开辟一个s1一样大小的空间
			_capacity = s._capacity;
			_size = s._size;
			strcpy(_str, s._str);
		}
		//析构函数
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}
		//重载赋值 s1 = s3
		string& operator=(const string& s)
		{
			if (this != &s)//避免this和自己赋值
			{
				char* tmp = new char[s._capacity + 1];
				strcpy(tmp, s._str);
				delete[]_str;
				_str = tmp;
				_size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}
		//返回字符串的首地址
		const char* c_str()const
		{
			return _str;
		}
		//返回字符串的长度
		size_t size()const
		{
			return _size;
		}
		//返回容量
		size_t capacity()const 
		{
			return _capacity;
		}
		//清理
		void clear()
		{
			_size = 0;
			_str[0] = '\0';
		}
		//重载[]
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}
		//开指定的容量
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp,_str);
				delete[] _str;
				_capacity = n;
			}
		}
		//Resize string
		void resize(size_t n, char ch = '\0')
		{
			if (n > _size)
			{
				reserve(n);
				for (size_t i = _size;i < n;++i)
				{
					_str[i] = ch;
				}
				_size = n;
				_str[_size] = '\0';
			}
			else
			{
				_str[n] = '\0';
				_size = n;
			}
		}
		//尾插
		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(_size + len);
			}
			strcpy(_str + _size, str);//这里拷贝的时候要注意拷贝的起始位置
			_size += len;
		}
		//这里注意+=可能会有二种传参
		string& operator+=(char ch)
		{
			Push_Back(ch);
			return *this;
		}
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}
		//插入数据
		string& 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[pos] = ch;
			++_size;
			return *this;
		}
		//插入字符串
		string& insert(size_t pos, const char* str)
		{
			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;
			}
			//拷贝从字符串中删除字符
			strncpy(_str + pos, str, len);
			_size += len;
			return *this;
		}
		//从字符串中删除字符
		string& erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			//当len很大或者直接删完
			if (len == npos || len >= _size - pos)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
			return *this;
		}
		//查找
		size_t find(char ch, size_t pos = 0)const
		{
			assert(pos < _size);
			while (pos < _size)
			{
				if (_str[pos] == ch)
					return pos;
				++pos;
			}
			return  npos;//没找到
		}
		//查找字符串
		size_t find(const char* str, size_t pos = 0)const
		{
			assert(pos < _size);
			const char* ptr = strstr(_str + pos, str);//用strstr暴力查找
			if (ptr == nullptr)
			{
				return npos;
			}
			else
			{
				return ptr - _str;
			}
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
		const static size_t npos = -1;
	};
	//重载<<和>>
	ostream& operator<<(ostream& out, const string& s)
	{
		for (size_t i = 0; i < s.size(); ++i)
		{
			out << s[i];
		}

		return out;
	}	
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char buff[128] = { '\0' };
		size_t i = 0;
		char ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			if (i == 127)
			{
				s += buff;
				i = 0;
			}
			buff[i++] = ch;
			ch = in.get();
		}
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}

评论 46
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小蜗牛~向前冲

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

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

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

打赏作者

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

抵扣说明:

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

余额充值