c++ string的使用

1. STL是什么

STL(standard templete library-标准模板库);是c++标准库的重要组成部分,不仅是一个可以复用的组件库,而且是一个包含了数据结构和算法的软件框架。
在这里插入图片描述
最主流:容器,算法
其中,容器就是数据结构。
算法就是排序,交换之类的。

2. string 是什么

2.1. 引入

c语言中,字符串是以 ‘\0’ 结尾的一些字符的集合。为了方便操作,c标准库中提供了一些 str 系列的库函数,strlen,strcmp,stract,strcpy。
但是这些库函数与字符串是分离开的,字符串可以随意创建,但是关于字符的函数在string.h 的头文件中。
而且 字符串的空间还需要用户自己管理,稍不留神可能就会越界访问。

2.2. string 的优势

string 是管理字符的顺序表。
可以理解为c++库中有一个关于字符串的类,需要的时候可以随时调用。
在c++中,又关字符串的题目通常是以 string 类的形式出现,而且在常规工作中,为了简单,方便,快捷,基本都是使用 string 。

  • 这里使用cplusplus网站上的文档,来使用对应的函数。
    以下是 string 所支持的函数
    在这里插入图片描述
    下面会对string中的函数类别进行简单说明。
  1. 支持的类型
    在这里插入图片描述
  2. 默认构造函数
    在这里插入图片描述
    其中,因为函数重载,每个函数内都有多个函数重载,方便类型的识别。

比如默认构造函数,拷贝构造函数
在这里插入图片描述
这里不仅支持单个字符,也支持字符串,常量字符串,还有多种情况。
析构函数
在这里插入图片描述
3. 迭代器
在这里插入图片描述 4. 容量在这里插入图片描述
5. 大小关系
在这里插入图片描述
string 中一共设计了100多个函数,不过并不是所有的函数都有用,下面会介绍些比较常用的函数。

3. stirng 的使用

3.1. 基本使用

string 需要包头文件 string.h
在这里插入图片描述
string 也是个类模板,原型为 basic_string的类。
因为平时使用的是字符串,所以这里直接用char类型实例化了。

3.1.1. 创建string对象

在这里插入图片描述

  • 构造无参的string
void test_string1()
{
	string s1;
}
  • 构造有参的string,可以使用第4种,使用常量字符串去初始化
void test_string2()
{
	string s2("hello");
}

在这里插入图片描述
在string的库函数中,也实现了流插入和流提取,所以直接使用cout和cin输出和输入即可。

void test_string1()
{
	string s1;
	string s2("hello");

	cin >> s1;
	cout << s1 << endl;
	cout << s2 << endl;
}

在这里插入图片描述
string的字符串,空间是动态开辟出来的,也就是说没有静态数组容量不足或者大量浪费空间的问题。
而且含有构造和析构,也不需要我们手动开辟和释放,比c语言中提供的字符串方便的多。

3.1.2. 拼接字符串

库函数内也实现了 operator+的操作。
在这里插入图片描述
同样,也有函数重载,方便传入不同的类型。
对象+对象,对象+常量,对象+字符,函数重载都支持。

void test_string1()
{
	string s1;
	string s2;

	cin >> s1;
	cin >> s2;

	cout << s1 << endl;
	cout << s2 << endl;

	string s3 = s1 + s2;
	cout << s3 << endl;
}

在这里插入图片描述
输入 “我的”和“世界”,+ 就会自动将两个字符串拼接在一起。

operator + 不仅支持对象和对象的拼接,也支持和常量字符串相加。

string s4 = s1 + "new world";
cout << s4 << endl;

在这里插入图片描述
虽然c语言中也含有拼接字符串的函数 strcat ,但是可读性没有 ‘+’ 强,这方面string又比c语言中的字符串强。

  • 隐式类型
    构造函数,也可以采用隐式类型转换的形式进行初始化。
void test_string()
{
	string s1("hello");
	string s2 = "hello";
}

第一个直接拷贝构造
第二个构造+拷贝构造+优化。

3.1.3. 遍历字符串

operator[]也有定义在这里插入图片描述
string 实现了 operator[] 的重载,所以我们访问数组内的元素,可以使用下面的方法

void test_string()
{
	string s1("hello");
	cout << s1[3] << endl;
}

在这里插入图片描述
想要遍历string类,库函数内也有返回字符串长度的函数。
在这里插入图片描述

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

在这里插入图片描述
这里我们调用的 operator[] 传入的是没有 const 修饰过的,所以这里可以传入引用,直接诶对返回值进行修改,从而改变字符串中的元素。

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

在这里插入图片描述

3.1.4. 迭代器

迭代器iterator是遍历顺序结构的一种方式
在这里插入图片描述
begin是返回开始位置的指针,end是指向最后一个字符的下一位,它们都是函数。
在这里插入图片描述
这里使用迭代器可以完成对数据的输出

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

在这里插入图片描述
这里的++ 也被重载了,所以能指向想要指向的位置。
这里只看最后一行就行,上面是遍历数组中的输出结果。
数组也可以这样写

while( it < s1.end())
{
	//...
}

但是最好不要这样写。
迭代器是一种遍历顺序表的结构,也就是说,不管是现在学的数组,还是后面学的list(c++中实现的链表结构),也可以使用相同的写法对链表进行遍历。
string 在物理空间上是连续的,尾部的地址绝对是在最后面,所以这种写法可以使用。
但是 list 在物理空间上不连续,有可能尾节点的物理空间会在头结点前。对链表的指针进行判断 it<s1.end(),可能是没有意义的。

it != s1.end();

上面的写法才是通用的写法。
迭代器是所有容器都可以使用,[] 相对来说只是小众的写法(如果是树的话,拿数组的写法遍历看起来比较别扭)。

  • 反向迭代器
    上面是正向迭代器,我们输出的内容都是按照字符串的正向顺序进行输出。
    如果需要反向输出字符串的内容,也可以。
    需要使用下面的 rbegin 和 rend 函数
    在这里插入图片描述
void test_string3()
{
	string s1(“hello world");
	string::reverse_iterator rit = s1.begin();
	while( it != s1.rend() )
	{
		cout << *ret << "    ";
		++rit;
	}
	cout << endl;
}

在这里插入图片描述在这里插入图片描述
rbegin 是在最后一个字符的位置,rend是在第一个字符前。
但是rit的方向不是–,而是++
一般情况下,倒着遍历都会用反向迭代器,也可以对 rit --,进行正向输出,但是用起来不方便。

string::reverse_iterator rit = s1.rbegin()

上面定义 rit ,前面的数据类型很麻烦,但是我们知道 rbegin 肯定会返回这种类型,因此这里直接使用 auto 来定义 rit 。

auto rit = s1. rbegin()

3.1.5. 范围for

它会自动获取容器或者数组的范围,然后进行输出
自动判断结束,自动++
原理:编译器自动替换成迭代器

void string15()
{
	string s("hello world");
	for (auto ch : s)
	{
		cout << ch << "   ";
	}
	cout << endl;
	string::iterator rit = s.begin();
	while (rit != s.end())
	{
		cout << *rit << "   ";
		rit++;
	}
}

上面是范围for的输出,下面是迭代器的输出
在这里插入图片描述
可以看见,范围for 和 迭代器都调用了两个很相似的函数,间接说明范围for和迭代器本质上差不多,就是范围for相对来说写起来方便。

3.1.6. 传入参数

void func(string s)
{
}

一般情况下,不建议这样传参,这样传会有拷贝构造,会降低效率。
可以传入const修饰的引用,但是传入const 又会有新的问题

void func(const string& s)
{
	string::iterator it = s.begin();
	while(it != s.end())
	{
		cout << *it <<endl;
	}
	cout << endl;
}

在这里插入图片描述
这里我们传入的是const修饰的s调用的begin函数,就会返回一个const_iterator 类型的数据。
在这里插入图片描述
返回来的 无const 修饰的,可读可写
返回来的 有cosnt 修饰的,只可读,不可写。
如果我们想要接收 const 修饰的类型,我们就需要用 const 修饰的类型接收。

string::const_iterator it = s.begin();

如果是反向迭代器,如果不使用 cosnt ,虽然不会显示出来,但是也运行不了。
因此反向迭代器接收 const修饰对象调用的rbegin 函数 ,也需要用const修饰的对象接收。

string::const_reserve_iterator it2 = s.rbegin();

但是像上面这种定义类型,写的太麻烦了,如果我们心里清楚返回的类型,我们直接使用 auto 接收返回值即可。

auto it = s.begin();
void func()
{
	string::const_reserve_iterator it1 = s.rbegin();
	auto it2 = s.rbegin();
	
	while(it1 != s.rend())
	{
		cout << *it1 << endl;
		it1++;
	}
	cout << endl;
	
	while(it2 != s.rend())
	{
		cout << *it2 << endl;
		it2++;
	}
	cout << endl;
}

在这里插入图片描述
在c++11中,加入了const修饰的 cbegin,cend 函数,就是
在这里插入图片描述
在这里插入图片描述
cbegin 函数需要传入const 修饰的对象,然后返回 cosnt修饰的对象。

正向迭代器,反向迭代器,const修饰,非const修饰。

3.2. 构造拷贝

上面简单介绍了一下string创建对象,但是这么多重载,创建对象不止能那样创建,还有其他方法。
我们可以采用下面任何一个重载函数进行构造
在这里插入图片描述

void string4()
{
	striing s1("hello world");
	cout << s1 << endl;
	string s2(s1,6,5);
	cout << s2 << endl;
	string s3(s1,6,3);
	cout << s3 << endl;
}

在这里插入图片描述
在这里插入图片描述
这里,npos是缺省参数,如果我们在这里不传入npos的值,就会默认使用
在这里插入图片描述
这里的npos是静态成员变量,但是这里的长度显示为 -1。
size_t 是 无符号整形,和 unsigned 一样,这里的 -1 就是二进制全是1,也就是 2^32 的大小。

在这里插入图片描述

这里npos,“until the end of the string”,也就是默认是字符串的长度。

string s4(s1,6);
cout << s4 << endl;

除了使用默认的 npos ,也可以使用 size()-pos 直接算出来 pos 与最后一个字符的位置。
因为有这个缺省参数,这里可以不传入,比如下面的初始化。

void string4()
{
	string s1("hello world");
	string s2(s1,4);
	cout << s2 << endl;
}

在这里插入图片描述
这里就默认从第4个字符的位置,一直到字符串结尾。
在这里插入图片描述也可以指定多少个字符去初始化

void string4()
{
	string s1("hello world");
	string s2(10,'c');
	cout << s2 << endl;
}

在这里插入图片描述

也可以使用迭代器取区间初始化。

void string4()
{
	string s1("hello world");
	string s2(10,'c');
	cout << s2 << endl;
	string s3("s2.begin(),s2.end());
	cout << s3 << endl;
}

赋值也有多个重载
在这里插入图片描述

s2 = s1;
s3 = "xxx";
s4 = 'x';

赋值操作可以按照上面的写法使用。

3.3. string 的容量

在这里插入图片描述

1. length,size

在这里插入图片描述
在这里插入图片描述

size 和 length,一个是顺序表内个数,一个数字符串大小,正常情况下,两个是一样的。

cout << s3 <<endl;
cout << s3.size() << endl;
cout << s3.length() << endl;

这里 size 和 length 的输出结果一样。
最开始只是字符串的话,用 length 更好,但是为了适配所有的容器,就开始使用 size。

2. clear

在这里插入图片描述
clear 是只是对数据的清除,但是不会释放空间。

void string4()
{
	string s3(10,'c');
	cout << s3 << endl;
	cout << s3.size() << endl;
	cout << s3.capacity() << endl;

	s3.clear();
	cout << s3.size() << endl;
	cout << s3.capacity() << endl;
}

在这里插入图片描述
capacity string开辟的空间大小,size 字符串长度。
调用clear之后,字符串长度为0,容量大小不变。

3. max_size

在这里插入图片描述
能打印出来最大能开辟多大的空间。
也就是内存中最多能开多少空间,但是这个空间是写死的,不管开没开空间都会显示这个数。
但是如果已经开了一些空间,max_size 还是显示那个数,所以实际用处不是很大。

cout << s2.max_size() << endl;

在这里插入图片描述
注:不同平台下能开辟的空间大小都有不相同。

4. capacity

在这里插入图片描述

void string5()
{
	string s;
	size_t old = s.capacity();
	cout << old << endl;

	for(int i = 0 ; i < 100 ; i++)
	{
		s.push_back( i );
		if( s.capacity() != old )
		{
			cout << s.capacity() << endl;
			old =s.capacity();
		}
	}
}

在这里插入图片描述

注:这里的capacity,指的是有效容量,‘\0’的位置是不会算入的。
这里就按照capacity+1作为容量大小。
16,32,48,71,106
最开始扩了2倍,后面都是1.5倍的扩。

5. reverse

reserve 保留
reverse 颠倒(迭代器那边使用)
在这里插入图片描述
请求改变容量
如果最开始就知道了需要多少空间

s.reserve(100);

直接申请100的空间,但是因为在 vs 下内存会自动对齐,所以这里会申请112的空间。
在这里插入图片描述
g++ 下要多少给多少
vs 下会多给
reserve 不同的平台要求不同,一般情况下容量是不能缩小的。

s.reserve(100);
s.reserve(10);

在这里插入图片描述
但是这里的空间都是被使用的,也就是内部有数据,如果没数据,是可以缩小的。
但是缩小的代价比较大,会异地开辟空间,然后把空间的内容复制过去。

6. resize

在这里插入图片描述
和reserve类似,但是是对size,也就是长度进行请求。
rsize 的情况相对来说会复杂点。

  • resize 的大小大于原空间
void string6()
{
	string s("hello world");
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;

	s1.resize(13);
	cout << s1.resize() << endl;
	cout << s1.capacity() << endl;
}

函数重载,有两个函数。
一个不传入字符,多出来的空间会用赋值 ‘\0’
在这里插入图片描述
如果传入字符,新的空间就会使用传入的字符来代替。

s1.resize(13,'x);
cout << s1.size() << endl;
cout << s1.capacity() << endl;
cout << s1 << endl;

在这里插入图片描述
如果传入的数量大于capacity,就会自动扩容

s1.resize(20,'x');

在这里插入图片描述

  • resize小于原来的size
    这种情况下,起到的就是删除作用。
s1.resize(5);
cout << s1.size() << endl;
cout << s1.capacity() << endl;
cout << s1 << endl;

在这里插入图片描述
这里原本size大小是11,但是resize的空间只有5,后面的空间就被删了。
如果原本内部是没有空间和数据的,resize可以起到初始化的作用。

string s2();
s2.resize(10,'L');
cout << s2.size() << endl;
cout << s2.capacity() << endl;
cout << s2 << endl;

在这里插入图片描述

7. at

在这里插入图片描述
at这个函数和 [] 类似,但是如果pos越界了,就会抛异常。
而[] 会进行断言,直接结束程序。

s2[0];
s2.at(0);

两种访问的写法都可以。

8. append

在字符后面插入字符串,或者字符
在这里插入图片描述

void string7()
{
	string s;
	s.append("hello");
	s.push_back('x');
	cout << s << endl;
}

在这里插入图片描述
append 支持传入字符串和单个字符,也支持传入string对象。
但是有 += 的操作,+= 可以直接实现在对象后面插入字符串/字符,一般该函数不常用。
== 非成员函数 ==

9. operator

在这里插入图片描述
operator+ , >> , << , swap ,都属于非成员函数。
operator+ 能少用就少用,使用 + 会进行拷贝构造,效率比较低。
即使是有优化,但是还是有两次拷贝,不是很建议多用。
虽然 operator+ 完全可以写成成员函数,但是库里面确实是非成员函数。

10.swap

如果我们有两个字符串,需要进行交换

void string5()
{
	string s1("hello");
	string s2("world");

	cout << s1 << endl;
	cout << s2 << endl;

	swap(s1,s2);

	cout << s1 << endl;
	cout << s2 << endl;
}

在这里插入图片描述
库里面的 swap 支持这个自定义类型的互换,但是这样的互换会很麻烦。
这是swap的模版
在这里插入图片描述
会进行三次深拷贝,效率很慢。
库里面也提供了swap函数
在这里插入图片描述
这个函数不会直接对两个对象的内容进行修改,是直接对对象的指针进行修改。

void string5()
{
	string s1("hello world");
	string s2("我的世界");
	cout << s1 << endl;
	cout << s2 << endl;
	printf("s1:%p\n", s1.c_str());
	printf("s2:%p\n", s2.c_str());
	//swap(s1, s2);
	s1.swap(s2);
	cout << s1 << endl;
	cout << s2 << endl;
	printf("s1:%p\n", s1.c_str());
	printf("s2:%p\n", s2.c_str());
}

string自带的swap会进行首地址的交换。
两个指针调换,就不需要拷贝之类的环节了。
上面的swap是成员函数,而在非成员函数中也有个swap函数
在这里插入图片描述
这个swap函数限制了,传入的两个参数。
因为非string的swap函数,实际上是模版,每次使用需要实例化,但是string中的swap函数,是直接存在的,所以直接使用 swap就是使用string的非成员函数的swap。

11. assign

在这里插入图片描述

复制,但是平时不常用

void string8()
{
	string s1("xxxxxxxx");
	string s2 = "hello world";
	s1.assign(s2);
	cout << s1 << endl;
}

在这里插入图片描述
这里会直接把s2 的内容复制到s1里面。
assign也可以传入pos位置和后面需要复制的大小。

void string8()
{
	string s1("xxxxxxxx");
	string s2 = "hello world";
	s1.assign(s2,3,5);
	cout << s1 << endl;
}

12. insert

插入
在这里插入图片描述
如果想要头插一个字符,就可以像下面的写法

void string9()
{
	string s("hello world");
	s.insert(0,1,'x');
	s.begin(s.begin(),'c');
	cout << s << endl;
}

在这里插入图片描述

第一种写法,是在pos位置,插入一个字符’x’。
第二种写法,是在begin()位置插入一个字符
但是对于字符串来说少用,数据向前覆盖,效率太慢。

13. erase


删除某个位置

void string8()
{
	string s("hello world");
	s.erase(2,3);
	s.erase(s.begin());
	cout << s << endl;

在这里插入图片描述
第一种写法,第二个位置往后删除3个空间
第二种写法,删除begin()位置

14. replace

在这里插入图片描述
replace 代替

void string11()
{
	string s("hello world");
	s.replace(5,1,"666");
	cout << s << endl;
}

用 “666” 替换第5个位置的数据。
在这里插入图片描述
replace,insert,erase 都尽量少使用,因为涉及到数据移动的问题,效率都不高。
接口设计比较多,需要时可以查文档。

  • pop_back//尾删
  • shrink_to_fit//缩容
  • c_str兼容c语言的

15. find系列

  1. find
    在这里插入图片描述
    如果想要查找一共文件名的后缀:
test.c

使用find即可
在这里插入图片描述

void string11()
{
	string s("test.c"):
	size_t i = s.find();
 	string s1 = s.substr(i);
 	cout << s1 << endl;
}

在这里插入图片描述
substr 可以获取pos位置到len为止的字符串,len为缺省参数,默认为npos,所以默认为可以获取从pos 到字符串结束的位置。
在这里插入图片描述
此时,我们通过 find 找到了 ‘.’ 的下标 ,然后将 i 位置到字符串结尾作为新的字符串给s1。
但是如果有多个 ‘.’ ,此时就不能直接使用find查找。
find 会从字符串开始位置向后查找,找到就返回,多个相同的字符,我们可以从后向前查找。

test.c.tar.zip
  1. rfind
    我们可以使用 rfind 从 后向前查找
    在这里插入图片描述
void string1()
{
	string s("test.c.tar.zip");
	size_t i = s.rfind('.');
	string s1 = s.substr(i);
	cout << s1 << endl;
}

在这里插入图片描述
注意substr传入一个参数是从当前位置到结尾,两个参数是区间的范围。
这里返回的 size_t i 和 j 一定要注意
如果没有找到下面的substr就没有意义,因此需要我们判断一下返回值

void string1()
{
	string s("test.c.tar.zip");
	size_t i =s.rfind('.');
	if( i !=string::npos )
	{
		string s1 = s.substr(i);
		cout << s1 << endl;
	}
}
  1. find_first_of
    在这里插入图片描述
    find_first_of 查找这个子串中的任意一个字符
void string2()
{
	string str("fiklgdilshgaiolgfgjdksafgaskudfgakusdfagkusf");
	size_t found = str.find_first_of("aeiou");
	while(found != string::npos)
	{
		str[found] = '*';
		found = str.find_first_of("aeiou",found+1);
	}
	cout << str << endl;
}

在这里插入图片描述
find_first_of 查找前面字符串是否有传入字符串中的其中一个,找到了返回下标。
4. find_last_of
和上面 find_first_of 一样的,但是是从后往前找。
5. find_first_not_of
查找不是当前字符串内的
在这里插入图片描述
find系列,一般用的比较少

16. getline

cin 和 cout 以及 sacnf 和 printf
虽然可以完成输入输出,但是如果想要输入多段字符串“hello world”,中间的空格会被默认停下。

int main()
{
	string s1 , s2;
	cin >> s1 >> s2;
	cout << s1 << endl;
	cout << s2 << endl;
}

在这里插入图片描述
这里输入一行的 hello world ,会默认读取为两段。
此时就可以使用getline函数
在这里插入图片描述
获取一行,遇到空格不结束,遇到换行才结束

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

在这里插入图片描述
使用getline就能实现只有换行才算读取结束。
如果不想以换行或者空格结束,想以某个特定的符号结束,也可以。
在这里插入图片描述

int main()
{
	string s1;
	string s2;
	getline(cin,s1,'x');
	getline(cin,s2,'x');
	cout << s1 << endl;
	cout << s2 << endl;
	return 0;
}

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值