STL—string类

目录

1、为什么要学习string?

2、介绍

3、特点

4、string类常用构造函数

5、string类的析构函数

6、string类对象的遍历和访问

7、string类对象的容量操作

8、at和[ ]

9、增删查改(注意很多函数都重载了好几个函数,下述只举例常见的,具体可查阅文档)

10、assign:赋值

11、replace和find联合使用,将字符串中的所有空格替换成%

12、c_str():将string类型转化为const char*

13、substr:从字符串pos位置开始,将往后len个字符组成新字符串返回。

(1)、找出文件名的后缀

(2)、将网址的三部分分离(协议、域名、文件路径)

14、find_first_of 和 find_last_of

15、find_first_not_of 和 find_last_not_of

16、getline:读取一段字符串,遇到换行结束

17、解决浅拷贝的问题

18、注意编码问题

19、string类模拟实现



1、为什么要学习string?

C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP(面向对象编程)的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。

2、介绍

1. 字符串是表示字符序列的类。
2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。
3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型
4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数
5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。

3、特点

1. string是表示字符串的字符串类
2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
3. string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator> string;
4. 不能操作多字节或者变长字符的序列。
5、在使用string类时,必须包含#include头文件以及using namespace std;

4、string类常用构造函数

1、string() :构造空的string类对象,即空字符串(重点)
2、string(const char* s) :用C-string来构造string类对象(重点)
3、string(const string&s) 拷贝构造函数(重点)
4、string(size_t n, char c) :string类对象中包含n个字符c
	//空字符串
	string test1;
	//用C-string构造string类对象
	string test2("hello");
	//拷贝构造
	string test3 = test1;
	string test4(test2);
	//使string对象包含n个字符
	string test5(5, 'a');

注意:string类是重载了流插入和留提取运算符,所以其对象可以直接使用cout和cin;

这里只举例常见的构造,具体可前往官网查看文档。

5、string类的析构函数

因为string类支持cin,所以底层实现是右new操作符动态开辟空间的,所以需要析构函数~string()释放,但析构函数是自动调用的,所以平时可以不管。

6、string类对象的遍历和访问

(1)、利用流插入运算符重载

(2)、string类运算符重载了[ ],所以可以用[ ]进行读写(注意[ ]重载提供了两个版本,有个版本用const修饰了this指针,返回值也会const引用,所以只可读不能写,具体查看文档)

int main()
{
	string s1("hello world");
	//读
	for (int i = 0; i < s1.size(); i++)
	{
		cout << s1[i] << " ";
	}
	cout << endl;
	//写:字符串逆序
	int begin = 0;
	int end = s1.size() - 1;
	while (begin < end)
	{
		swap(s1[begin], s1[end]);
		begin++;
		end--;
	}
	cout << s1;
	return 0;
}

(3)、利用迭代器iterator(用法像指针):

int main()
{
	string s1("hello");
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}

这里begin()指向第一个位置,end()指向数据内容的后一个位置;

因为[ ]只适合部分容器,如树形、哈希结构、链式结构等等只能用迭代器,迭代器才是容器访问的主流。

知道了迭代器,那么容器逆序可以用一个函数模版:

reverse(s1.begin(), s1.end());

reverse函数需要左闭右开的区间,而迭代器的begin和end刚好满足。

需要注意迭代器的两个写法:

(4)、范围for(适用于多种容器,相比上述几种方式是最便捷的,但底层会替换成迭代器)

string s1("hello");

for (auto e : s1)
{
	cout << e << " ";
}
cout << endl;

7、string类对象的容量操作

(1)、size(重点) :返回字符串有效字符长度。
(2)、length :返回字符串有效字符长度。
(3)、capacity :返回空间总大小。
(4)、empty (重点):检测字符串释放为空串,是返回true,否则返回false。
(5)、clear (重点) :清空有效字符。
(6)、reserve (重点) :为字符串预留空间,但不会缩容,也不会影响字符串原数据,缩容代价大。
(7)、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’(ascll码为0,此时的'\0'是有效字符,会被size()统计,不是标识字符)来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
(4)、reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
注意:
(1)、通过下图几个操作我们会发现string对象当空间不足时,会自动扩容,不同编译器扩容规则不同,vs2022大概是1.5倍:
如上图,第一次预留5个空间,但结果是15,第二次预留16个空间,但容量却是31,这表明,当string对象的空间不够时,它会以一个规则来扩容。
这也表明了如果我们确定自己需要多少空间,就可以用reserve提前开好空间,就少去扩容的步骤,提高效率。

8、at和[ ]

两者用法相同,但有一些区别:

(1)、用[ ],越界会直接报错。

(2)、用at,越界会抛异常。

9、增删查改(注意很多函数都重载了好几个函数,下述只举例常见的,具体可查阅文档)

(1)、增:

int main()
{
	string str1;

	//(1)、
	//push_back插入一个字符
	str1.push_back('1');
	str1.clear();

	//(2)、
	//append插入字符串
	str1.append("23");
	//append插入一个string对象
	string str2("56789");
	str1.append(str2);
	str1.clear();
	//append插入字符串的一部分,注意是左闭右开区间
	str1.append(str2.begin() + 2, str2.end() - 1);
	cout << str1 << endl;
	str1.append("abcd", 0, 2);
	cout << str1 << endl;

	//(3)、
	//+=运算符重载
	str1 += "1234";
	str1 += "s2";

    //(4)、
    //实现在某个位置插入字符串。
    string str1("hello");
    str1.insert(5, "xxxx");
    //在头部插入5个A
    str1.insert(0, 5, 'A');
    //用迭代器头插一个字符
    str1.insert(str1.begin(), 'B');

	return 0;
}

(2)、删

int main()
{
	string str1 = "hello";
	//删除字符串str1的0位置处往后三个字符
	str1.erase(0, 3);
	cout << str1;
	//尾删:pop_back
	string str2("hello");
	str2.pop_back();
	cout << str2 << endl;
	return 0;
}

(3)、查:迭代器、[ ]

(4)、改:迭代器、[ ]

10、assign:赋值

string str2;
//赋值一串字符串
str2.assign("hello");
cout << str2 << endl;
//赋值字符串的一段区间,左闭右开
str2.assign("abcd", 0, 3);
cout << str2 << endl;

不怎么使用,因为赋值直接构造更方便。

	string str3("hello");
	str3 = "a";
	cout << str3;

11、replace和find联合使用,将字符串中的所有空格替换成%

replace:替换某个字符或字符串。

find:查找某个字符,并返回下标。若没有找到,返回string类的静态成员npos(值为-1)。

//将字符串中的所有空格替换成%

	string str3("hello world  I love you");
	size_t pos = 0;
	while ((pos = str3.find(' ', pos + 1)) != string::npos)
	{
		str3.replace(pos, 1, "%");
	}
	cout << str3 << endl;

但平时一定要少用replace,因为每次都要重复挪动很多数据,效率及其低。

下面是不使用replace,而使用+=运算符重载进行实现。

string str4("hello world  I love you");
//借助临时字符串
string str5;
for (auto ch : str4)
{
	if (ch == ' ')
	{
		str5 += '%';
	}
	else
	{
		str5 += ch;
	}
}
cout << str5 << endl;
//如果想要在原字符串上进行修改,直接用成员函数swap,注意和算法函数swap不同
str4.swap(str5);
cout << str4 << endl;

12、c_str():将string类型转化为const char*

例题1:打印文件内容

string fname = "test.cpp";
//因为fopen函数的第一个参数是const char*类型,而string类型不能直接给其赋值,所以用c_str返回c式类型
FILE* f = fopen(fname.c_str(), "r");
char ch = getc(f);
while (ch != EOF)
{
	cout << ch;
	ch = getc(f);
}

13、substr:从字符串pos位置开始,将往后len个字符组成新字符串返回。

首先我们需要了解:

len参数的缺省值是npos(size_t中是整形最大值,int中是-1,与补码有关)

(1)、找出文件名的后缀

	string str6 = "test.cpp";
	size_t pos = str6.find('.');
	string str7 = str6.substr(pos + 1);
	cout << str7 << endl;
	//文件名有多个点的情况,用rfind,反向寻找
	string str8 = "test.tar.txt";
	size_t pos1 = str8.rfind('.');
	string str9 = str8.substr(pos1 + 1);
	cout << str9 << endl;

注意这里第二种情况,文件名中有多个点,这时就用了另一个成员函数rfind(),这个函数是从字符串尾部向头部进行查找,但返回的下标还是字符在字符串中的下标。

(2)、将网址的三部分分离(协议、域名、文件路径)
string str1 = "https://legacy.cplusplus.com/info/";
string sub1, sub2, sub3;

size_t pos1 = str1.find(':');
sub1 = str1.substr(0, pos1);
cout << sub1 << endl;

size_t pos2 = str1.find('/', pos1 + 3);
sub2 = str1.substr(pos1 + 3, pos2 - (pos1 + 3));
cout << sub2 << endl;

sub3 = str1.substr(pos2 + 1);
cout << sub3 << endl;

14、find_first_of 和 find_last_of

find_first_of:是从头向尾返回字符串A中在字符串B中出现的所有字符的下标

find_last_of:是从尾向头返回字符串A中在字符串B中出现的所有字符的下标

	string str1 = "abcdeabcdeabcde";
	size_t pos = str1.find_first_of("abc");
	while (pos != string::npos)
	{
		str1.replace(pos, 1, "*");
		pos = str1.find_first_of("abc", pos + 1);
	}
	cout << str1 << endl;

15、find_first_not_of 和 find_last_not_of

这两个函数的功能就是与上述两个函数相反:

返回字符串A中指定字符串B中出现的所有字符的下标。

	string str1 = "abcdeabcdeabcde";
	size_t pos = str1.find_first_not_of("abc");
	while (pos != string::npos)
	{
		str1.replace(pos, 1, "*");
		pos = str1.find_first_not_of("abc", pos + 1);
	}
	cout << str1 << endl;

16、getline:读取一段字符串,遇到换行结束

(1)、头文件:<string>

(2)、该函数主要用于读取一段带有空格符的字符串,平时用的cin留提取运算符是以空格符和换行符作为分隔,所以读取不了空格符。而该函数只以换行作为分隔。

int main()
{
	string str;
	getline(cin, str);
	cout << str << endl;
	return 0;
}

注意有两个参数,第一个是流对象,第二个字符串对象。

17、解决浅拷贝的问题

浅拷贝有两大问题:

(1)、会析构两次;

(2)、多个对象指向同一块空间,会互相影响。

解决:

解决(1):引用计数,记录这块空间有多少个对象在引用,当引用计数等于01时才析构。

解决(2):写实拷贝,即刚开始进行浅拷贝,哪个对象要对这份空间进行修改,就叫它重新拷贝一份。这样做的意义在于,后续可能没有对象会进行修改这时就赚了效率。

18、注意编码问题

19、string类模拟实现

	typedef char* iterator;
	typedef const char* const_iterator;
	class string
	{
	private:
		size_t _size;
		size_t _capacity;
		char* _str;
		const static int npos = -1;
	public:
		string(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_size + 1];
			strcpy(_str, str);
		}

		//传统写法

		/*string(const string& str)
		{
			_str = new char[str._capacity + 1]; 
			strcpy(_str, str._str);
			_size = str._size;
			_capacity = str._capacity;
		}
		string& operator=(const string& str)
		{
			if (this != &str)
			{
				char* tmp = new char[str._capacity + 1];
				strcpy(tmp, str._str);
				delete[] _str;
				_str = tmp;
				_size = str._size;
				_capacity = str._capacity;
			}
			return *this
		}*/

		//现代写法
		string(const string& str)
		{
			string tmp(str);
			swap(tmp);
		}
		string& operator=(string str)
		{
			swap(str);
			return *this;
			
		}

		~string()
		{
			delete[] _str;
			_size = 0;
			_capacity = 0;
		}
		//c_str()
		const char* c_str()const
		{
			return _str;
		}
		//size()
		size_t size()const
		{
			return _size;
		}
		//[ ]
		const char& operator[](size_t pos)const 
		{
			assert(pos < _size);
			return _str[pos];
		}
		char& operator[](size_t pos)
		{
			assert(pos <= _size);
			return _str[pos];
		}
		//迭代器
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		const_iterator begin()const
		{
			return _str;
		}
		const_iterator end()const
		{
			return _str + _size;
		}

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

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

		void append(const char* str)
		{
			size_t len = strlen(str);
			if (len+_size > _capacity)
			{
				reserve(len + _size);
			}
			strcpy(_str + _size, str);
			_size += len;
		}
		string& operator+=(const char c)
		{
			push_back(c);
			return *this;
		}
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}

		void insert(size_t pos, const char c)
		{
			assert(pos >= 0);
			if (_size == _capacity)
			{
				size_t newCapacity = _capacity == 0 ? 4 : 2 * _capacity;
				reserve(newCapacity);
			}

			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				end--;
			}
			_str[pos] = c;
			_size++;

		}

		void insert(size_t pos, const char* str)
		{
			assert(pos <=_size);
			size_t len = strlen(str);
			if (len + _size > _capacity)
			{
				reserve(len + _size);
			}
			int end = _size;
			while (end >= (int)pos)
			{
				_str[end + len] = _str[end];
				end--;
			}
			strncpy(_str + pos, str, len);
			_size += len;
		}

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

		void swap(string& str)
		{
			std::swap(_str, str._str);
			std::swap(_size, str._size);
			std::swap(_capacity, str._capacity);
		}

		size_t find(const char* str,size_t pos = 0)
		{
			const char* tmp = strstr(_str + pos, str);
			if (tmp == nullptr)
			{
				return npos;
			}
			else
			{
				return tmp - _str;
			}
		}
		string substr(size_t pos, size_t len = npos)
		{
			assert(pos <= _size);
			size_t end = pos + len;
			if (len == npos || len + pos > _size)
			{
				end = _size;
			}
			string tmp;
			tmp.reserve(end - pos);
			for (size_t i = pos; i < end; i++)
			{
				tmp += _str[i];
			}
			return tmp;
		}

		void clear()
		{
			_size = 0;
			_str[0] = '\0';
		}

	};

	ostream& operator<<(ostream& out, const string& str)
	{
		for (auto ch : str)
		{
			out << ch;
		}
		return out;
	}

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

  • 33
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 16
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

成工小白

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

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

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

打赏作者

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

抵扣说明:

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

余额充值