C++之string类的使用

1.为什么要学string类

我们所学的字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库提供了一些str系列的库函数,但是这些库函数与字符串是分离的,不太符合面向对象的思想,而且底层空间需要用户自己管理,稍不留神可能还会访问越界。

因此C++标准库提供了字符串类模板,为我们提供了各种功能的接口,而不在关心底层是如何实现的,方便我们去使用。

在学习一个模板的时候,我们需要了解这个模板提供了哪些接口,接口实现了什么功能,返回值等等,这里提供C++标准库,里面有各种模板的详细介绍。

2.标准库中的string类

在这里插入图片描述
basic_string是一个标准库提供的字符串类模板,charT是模板参数,表示的是字符类型,字符串由这种类型的一系列字符组成。后面的参数暂时不用管,后面再说。

在这里插入图片描述
这个类模板可以实例化上面4种类。

可能会有这样的疑问,字符串里面包含的都是普通字符,为什么要实例化4种类呢?

其原因是因为字符编码的问题。

补充知识

1.我们在C语言所接触最多的是ASCLL,包含了大小写字母,数字,标点符号等等…这套编码是由美国发明的,是符合老美文字习惯。但是为了将计算机推展到全世界,还需要包括其他国家文字。因此有了统一码(unicode)也叫万国码。

2.unicode有UTF-8、UTF-16、UTF-32三种将数字转换到程序数据的编码方案。一个字大小分别占1个字节,2个字节,4个字节。

3.我们使用最多是UTF-8,这种编码方案兼容ASCLL,还省空间。

在这里插入图片描述

以后根据字符编码不同,可以选择对应的实例化的类。

简单认识string类的构成

template<class T>
class basic_string
{
public:
	//各种成员函数
private:
	T* _str;
	size_t _size;
	size_t _capacity
};

typedef basic_string<char> string;

int main()
{
	//这两种是等价的
	basic_string<char> s;
	string s;
}

字符串类其实本质就是动态增长的数组,学过数据结构的数组,这里更容易理解。

使用string必须包含头文件

#include<string>

3.string类的接口的使用

string类每一种接口都有很多函数重载,下面主要讲述的是一些常用的需要重点掌握的,不需要看文档就会用的,其他没有讲述的如果需要用,在查文档。

3.1默认成员函数

在这里插入图片描述
默认成员函数包括:构造函数(包含拷贝构造函数),析构函数,赋值运算符重载函数

构造函数

在这里插入图片描述
拷贝构造函数是构造函数的重载,因此构造函数包含里包含拷贝构造函数。

在这里插入图片描述

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

在这里插入图片描述

赋值运算符重载函数

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

3.2容量操作

在这里插入图片描述

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

在这里插入图片描述

size和length都返回字符串有效字符长度,不包括’\0’。

这里可能会有疑问,一样功能的实现两个函数?

1.最开始求字符串有效字符长度只有length,但是有了STL(标准模板库)之后,涉及的模板多了起来,如求二叉树结点个数,链表长度,就不是能用length能表达清楚的了,所以有了size。
2.string为了符合STL(标准模板库),也有了size,在后面我们经常使用的是size。

在这里插入图片描述

当string类存放字符空间是动态开辟的,空间不够会扩容。

	//插入1000个字符,看是否扩容
	string s;
	size_t capacity = s.capacity();
	cout << "capacity:" << capacity << endl;
	for (int i = 0; i < 1000; ++i)
	{
		s.push_back('x');
		if (capacity != s.capacity())
		{
			capacity = s.capacity();
			cout << "change capacith:" << capacity << endl;
		}
	}

在这里插入图片描述
不同编译器扩容是不一样的。下面看看在Linux情况下,同一段代码扩容情况。

在这里插入图片描述
在这里插入图片描述
可以看见在Liunx下,同样代码,每次申请空间都是2倍。

我们知道频繁扩容是有代价的。如果我们预先知道开辟多大空间,就可以减少扩容,提高效率。

reserve为字符串预留空间。

在这里插入图片描述

empty判断字符串是为空。

在这里插入图片描述
clear清楚有效字符,清除之后是否缩容,看string类是如何规定。

在这里插入图片描述

resize,将有效字符的个数改成n个,多出的空间用字符c填充
resize有三种情况:
1.n<size
在这里插入图片描述

删除数据

2.size<n<=capacity
在这里插入图片描述

插入数据,如果没有指定用’\0’填充。

3.n>capacity
在这里插入图片描述

扩容+插入数据

3.3访问操作

在这里插入图片描述

函数名称功能说明
operator[] (重点)返回pos位置的字符,const string类对象调用

在这里插入图片描述

operator[]是常用的访问操作,不仅可以访问,还可以修改。

在这里插入图片描述

3.4遍历操作

void test_string4()
{
	string s("hello world");
	//遍历
	//1.普通遍历
	for (size_t i = 0; i < s.size(); ++i)
	{
		//字符+1
		s[i] += 1;
		cout << s[i] << " ";
	}
	cout << endl;

	//2.范围for遍历
	for (auto& ch : s)
	{
		//字符-1
		ch -= 1;
		cout << ch << " ";
	}
	cout << endl;

	//3.迭代器iterator
}

在这里插入图片描述

迭代器:是一种通用的访问方式。

iterators行为上像指针一样的类型
注意不同迭代器是不一样的。

	//3.迭代器iterator
	//正向迭代
	string::iterator it1 = s.begin();
	while (it1 != s.end())
	{
		*it1 += 1;
		++it1;
	}
	it1 = s.begin();
	while (it1 != s.end())
	{
		cout << *it1 << " ";
		++it1;
	}
	cout << endl;

在这里插入图片描述
返回字符串第一个数据位置

在这里插入图片描述
返回字符串最后一个有效数据的下一个位置。

	//反向迭代
	string::reverse_iterator it2 = s.rbegin();
	while (it2 != s.rend())
	{
		cout << *it2 << " ";
		++it2;
	}
	cout << endl;

在这里插入图片描述

在这里插入图片描述

rebegin返回有效字符最后一个位置

在这里插入图片描述

rend返回有效字符第一个位置

	//const正向迭代
	//只能遍历,不支持修改
	string::const_iterator it3 = s.begin();
	while (it3 != s.end())
	{
		cout << *it3 << " ";
		++it3;
	}
	cout << endl;

	//const反向迭代
	//只能遍历,不支持修改
	//string::const_reverse_iterator it4 = s.rbegin();
	//auto 根据右边推测左边的类型
	auto it4 = s.rbegin();
	while (it4 != s.rend())
	{
		cout << *it4 << " ";
		++it4;
	}
	cout << endl;

我们看见有的接口实现了两种类型,一个加const,一个没加const。
如果我们自己实现一个类也需要考虑要不要加上实现加const版本的函数。
在这里插入图片描述
总结:

1.只读功能函数,只提供const版本即可
2.只写功能函数,只提供非const版本即可
3.读写功能函数,需提供const+非const版本

3.5修改操作

在这里插入图片描述

函数名称功能说明
push_back在字符串后尾插字符c
append在字符串后追加一个字符串
operator+= (重点)在字符串后追加字符串str
insert插入字符串
erase删除字符串中的字符
assign为字符串指定一个新值,替换其当前内容。
swap交换字符串值

push_back
在这里插入图片描述

append 和构造函数的重载函数一样,但是常用的是第一个和第三个

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

operator+=是我们需要重点掌握的

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

insert 插入字符串
插入意味着挪动数据,所以string很少使用

在这里插入图片描述
在这里插入图片描述
剩下的可以自行了解文档。

erase删除字符串中的字符
也是要挪动属性,很少使用。

在这里插入图片描述

默认开始位置是0,默认长度是npos

在这里插入图片描述

size_t是无符号整型 ,-1会整型提升无符号整型,因此-1就变成了4294967295。

在这里插入图片描述

assign和replace都是替代原有的字符串,但是assign是清理掉原数组之后在赋值,而replace不清理原数组,直接替代。

在这里插入图片描述

在这里插入图片描述

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

swap交换字符串的值
在这里插入图片描述

在这里插入图片描述

3.6字符串操作

在这里插入图片描述

函数名称功能说明
c_str(重点)返回C格式字符串
find + npos(重点)从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
rfind从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
substr在str中从pos位置开始,截取n个字符,然后将其返回

c_str返回c格式字符串。

在这里插入图片描述

注意返回值是 const类型。

在这里插入图片描述

find,正向在字符串中查找内容

在这里插入图片描述
找到返回第一个匹配的第一个字符的位置。找不到返回string::npos

在这里插入图片描述

rfind,反向在字符串中查找内容。

在这里插入图片描述

在这里插入图片描述

**find_first_of,查找字符串中的字符,该函数和find不一样,这个函数会搜索参数中指定的任何字符匹配的第一个字符。。当指定位置时,搜索仅包括位置位置处或位置之后的字符,忽略位置之前可能出现的任何字符。**当作了解即可,用的很少。

在这里插入图片描述

搜索成功返回匹配字符的位置,不成功返回npos

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

substr生成子字符串
返回一个新构造的字符串对象

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

3.7非成员函数

在这里插入图片描述

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

operator>>从流中提取字符串

在这里插入图片描述
operator<<将字符串插入流
在这里插入图片描述

在这里插入图片描述
为什么输的是hello world,结果只打印处理hello呢?

cin和scanf都是输入,cin标准化输入,scanf格式化输入。
cin取数据时会忽略空格和回车继续输入。
scanf取数据时遇到回车、空格就会停止。
为了使cin和scanf一样,库里面实现的operator<<就针对做了特殊处理。

如果就想拿到整个字符串,使用getline函数将行从流转换为字符串
在这里插入图片描述

在这里插入图片描述

relational operators是字符串大小比较的接口
在这里插入图片描述
在这里插入图片描述

4.vs和g++下string结构的说明

4.1vs下string结构

void test_string1()
{
	string s1("1111");
	string s2("222222222222222222222222222222222");
	cout << sizeof(s1) << endl;
	cout << sizeof(s2) << endl;
}

这里s1和s2大小是多少呢?

类的大小=成员变量+结构体内存对齐;

首先肯定s1和s2大小是一样的。
string成员变量:

private:
	T* _str;
	size_t _size;
	size_t _capacity
};

一个指针_str(32位下:4字节 ,64位下:8字节),两个size_t的变量。加起来然后再内存对齐。(假设32位机器下)结果应该是12字节。
在这里插入图片描述
但是结果是28。这是怎么回事?

这里就不得不提vs下string结构

//string结构
char _buff[16]
char* _ptr
size_t _size
size_t capacity

在这里插入图片描述
结果是28,因为12加上buff空间的大小(16)

string这样设计的原因是以空间换时间。
1.<16 存在buff中,这样避免在堆上开辟小空间,然后又释放造成内存碎片。并且效率高
2.>= 16 存在_Ptr指向的空间。

4.2g++下string结构

#include<iostream>
#include<string>
using namespace std;

int main()
{
     string s1("1111");
     string s2("22222222222222222222222222222222");                                                                                                               
 
     cout<<sizeof(s1)<<endl;
     cout<<sizeof(s2)<<endl;
     return 0;
 }

在这里插入图片描述

结果是8,但是在g++下string是通过写时拷贝实现的,string对象只占4个字节。内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:

struct _Rep_base
{
 size_type _M_length;
 size_type _M_capacity;
 _Atomic_word _M_refcount;
};

在这里插入图片描述

  1 #include<iostream>  
    #include<stdio.h>
  2 #include<string>  
  3 #include<list>  
  4 using namespace std;  
  5   
  6 int main()  
  7 {  
  8     string s1("1111");  
  9    // string s2("22222222222222222222222222222222");  
 10     string s2(s1);  
 11     cout<<sizeof(s1)<<endl;  
 12     cout<<sizeof(s2)<<endl;  
 13   
 14     printf("%p\n",s1.c_str());  
 15     printf("%p\n",s2.c_str()); 
 16		return 0;
 17}  

在这里插入图片描述
发现g++下,string拷贝构造默认是浅拷贝。这样极致利用了空间。

浅拷贝如果要析构怎么办?
这里最前面有一个地方存放引用计数,当要析构的时候,就-1,当引用计数为1时,才会真正调用析构。
在这里插入图片描述

修改一下代码

  1 #include<iostream>
  2 #include<string>
  3 #include<list>
  4 #include<stdio.h>
  5 using namespace std;
  6 
  7 int main()
  8 {
  9     string s1("1111");
 10    // string s2("22222222222222222222222222222222");
 11     string s2(s1);
 12     cout<<sizeof(s1)<<endl;
 13     cout<<sizeof(s2)<<endl;
 14     
 15     printf("%p\n",s1.c_str());
 16     printf("%p\n",s2.c_str());
 17     
 18     s2[0]++;                                                                                                                                                     
 19     printf("%p\n",s1.c_str());                    
 20     printf("%p\n",s2.c_str());
 21		return 0;
 22}      

在这里插入图片描述
这里涉及写时拷贝,如果不修改数据,就不会重新申请空间了。“赌徒思维”。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值