【C++】STL入门—— 一张图带你了解常用的string类函数

目录

思维导图 (建议收藏,复习小宝贝)

一、STL简介

1.什么是SLT

2.STL的六大组件

二、string类简介 

三、string类的常用接口

1. string类对象初始化操作

1.构造函数

2.析构函数 

3. 赋值重载函数

 2.string类对象的容量操作

1.字符串的检查

2.增容的机制

3.reserve和resize的用法 

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

1.元素的访问——[ 下标 ]

2.元素的访问——迭代器

3.元素的访问——范围for

4.string类对象的修改操作

5.string类对象的查找操作

6.string类非成员函数重载

7.string类的其他函数


思维导图 (建议收藏,复习小宝贝)

 

一、STL简介

1.什么是SLT

STL(standard template libaray-标准模板库)
是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。从根本上说,STL是一些"容器"的集合,这些"容器"有list、vector、set、map等,STL也是算法和其他一些组件的集合。

2.STL的六大组件

网上有句话说:“不懂STL,不要说你会C++”。STL是C++中的优秀作品,有了它的陪伴,许多底层的数据结构以及算法都不需要自己重新造轮子,站在前人的肩膀上,健步如飞的快速开发。  

二、string类简介 

        对于string,在C语言中我们是比较熟悉的;当我们对字符串进行处理时(如:strlen、strcpy、strcat等),但是这些库函数与字符串是分离开的,不太符合OOP(Object Oriented Programming,OOP,面向对象程序设计)的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

        在C++中,string是用来管理字符数组的一个类,是STL中的一个容器。string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,对于字符串的处理交由string而不是编程人员,大大提升了效率,所有学好STL是很有必要的。 

在使用string类时,必须包含#include<string>头文件以及using namespace std;

三、string类的常用接口

        在string类中包含100多种接口函数,但是在日常做题或日后的使用中,经常使用的比较少,我们必须要将这些常见的函数重点掌握,其他大概了解一下,如果有不懂的可以查看C++文档。

1. string类对象初始化操作

1.构造函数

        如下图所示,C++中string类提供了7种构造函数,我们在学习string类时,只需要将默认构造、拷贝构造和含参构造重点掌握及使用,其余了解即可;

函数名称 说明
1.string( ); 构造一个空的string类
2.string(const string& str); 拷贝构造(拷贝一个str的副本)
3.string(const string& str, size_t pos, size_t len=pos ); 从指定的pos位置向后开始拷贝,len采用了缺省参数,默认值是-1
4.string(const char* s); 用C-string来构造函数
5.string(size_t n, char c); 用连续n个字符c的副本填充字符串

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


int main() 
{
	string s1;//默认构造
	string s2("hello world");//含参构造
	string s3(s2);//拷贝构造

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

	string s4(s2, 2, 6);//从时s2字符串的第二个位置开始拷贝,拷贝6个;
	cout << s4 << endl;

	string s5(s2, 2);
	string s6(s2, 2, 100);//有多少给多少

	return 0;
}

这段代码中有两个地方需要了解一下:

string s4(s2, 2, 6);//从时s2字符串的第二个位置开始拷贝,拷贝6个;

string s5(s2, 2);
string s6(s2, 2, 100);

  string(const string& str, size_t pos, size_t len = npos );

        这是表格中第三个函数,从某个已经存在的字符串的第pos个位置向后进行拷贝,由于此函数的第三个形参(len)使用的是缺省参数,默认值是-1,又因为是size_t类型的,他就是整型的最大值,C++处理方式:当len有实参传递时,无论实参多大,向后拷贝的个数仅取决于字符串从pos位置向后的个数(有多少拷贝多少);当len没有实参传递时,默认是-1(整型的最大值)也是有多少拷贝多少;

 Npos是一个静态成员常数值,它是size_t类型元素的最大可能值。

2.析构函数 

 系统会自动调用析构函数

3. 赋值重载函数

函数名称 说明
string& operator=(const string& str); 用string对象进行赋值
string& operator=(const char* s); 用C-string进行赋值
string& operator=(char c); 用一个字符进行赋值
string str1, str2, str3;
str1 = "hello";			  // c-string
str2 = 'x';               // 用一个字符赋值
str3 = str1;              // 用一个对象进行

 2.string类对象的容量操作

常用函数 说明
1.size 返回字符串中有效的字符个数,不包含'\0'
2.length 返回字符串中有效的字符长度,不包含'\0'
3.max_size 最大能存储多少个字符
4.resize

将字符串的长度调整为n个字符:

        1.如果n小于当前字符串长度,当前值将缩短到第n个字符,删除第n个字符以外的字符。

        2.如果n大于当前字符串长度,延长最后插入当前内容尽可能多的字符需要达到的大小n。

        3.如果指定c, c的新元素初始化复制,否则,他们初始化值字符(null字符)。

5.capacity 返回分配的存储空间大小
6.reserve 开辟空间,只影响容量
7.clear 删除字符串的内容,使其成为一个空字符串(长度为0个字符)
8.empty 判断字符串是否为空

1.字符串的检查

int main()
{
	string s1;

	cin >> s1;
	cout << s1 << endl;
	//不包含最后作为结尾标识符的\0,计算的是有效字符长度
	cout << s1.size() << endl;        //求s1的字符个数
	cout << s1.length() << endl;      //求s1的字符长度
	cout << s1.max_size() << endl;    //求s1的最大能存储多少字符
	cout << s1.capacity() << endl;    //求s1的当前的容量
    cout << s1.empty() << endl;       //判空?
	s1.clear();                       //把有效数据给清掉,但是容量并没有清掉
    
	cout << s1 << "空的字符串" << endl;//检验

	return 0;
}

2.增容的机制

void TestPushBank()
{
	string s;
	size_t sz = s.capacity();
	cout << "capacity changed:" << sz << '\n';
	cout << "making s grow:\n";
	for (int i = 0; i < 1000; ++i)
	{
		//s.push_back('c');
		s += 'c';//尾插字符

		if (sz != s.capacity())
		{    //增容
			sz = s.capacity();
			cout << "capacity changed:" << sz << '\n';
		}
	}
}

 

        从上图中可以看出最初的容量是16(虽然图中显示的是15,它计算的是有效空间,其实忽略了'\0'),从第二次增容开始,32->48->71....,以1.5倍增容的;这是在VS的编译器下,如果是在Linux下,它是以2倍的形式增长的;

3.reserve和resize的用法 

void Test_String3()
{
	string s1;
	s1.reserve(100);//开辟100个空间

	string s2;
	s1.resize(100);
	//s1.resize(100,'x');//开辟100个字节的空间,并全部初始化为字符x

	/*
		reserve---开空间,影响的是容量
		resize---开空间,对这些空间给一个初始值'\0',也可以自己给值,进行初始化
	*/

	string s3("hello world");
	s3.reserve(100);

	string s4("hello world");
	s4.resize(100, 'x');
	//以上不会对hello world进行修改,即当增容的量比原始的容量大时,小时会删除数据
}

 注意:

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

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

1.元素的访问——[ 下标 ]

常用函数 说明 备注
1.operator[ ]         支持下标访问,返回对位于字符串中pos位置的字符的引用 如果string对象是const限定的,则函数返回一个const char&。否则,它返回一个char&
2.at         支持下标访问,返回对位于字符串中pos位置的字符的引用
3.back 返回对字符串最后一个字符的引用
4.front 返回对字符串的第一个字符的引用

当我们想要将一个字符串逐个字符打印出来时,通常想到是利用下标去访问

void Test_String1()
{
	string s1("hello world");
	//打印字符串
	for (size_t i = 0; i < s1.size(); ++i)
	{
		cout << s1[i] << " ";//  s1[i] 等价于 s1.operator[](i)
	}
	cout << endl;

	//修改字符串
	for (size_t i = 0; i < s1.size(); ++i)
	{
		s1.at(i) += 1;
		//或s1.operator[](i) += 1;
		//或s1[i] += 1;
	}

	cout << s1.front() << endl; //打印字符串第一个字符
    cout << s1.back() << endl;  //打印字符串最后一个字符
	cout << endl;
}

operator[ ]at 的效果是一样的,两者区别在于检查机制:

        operator[ ]:当发生越界访问时,会直接assert报错;

        at:当发生越界访问时,会直接抛异常;

2.元素的访问——迭代器

 

        访问string对象除了利用下标的方式还可以使用迭代器(iterator),迭代器是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。迭代器修改了常规指针的接口,所谓迭代器是一种概念上的抽象;

 在使用迭代器时,需要指定类域,它是在类里面定义的;

void Test_String1()
{
    /**********正向迭代器**********/

    string s1("hello world");
	string::iterator it = s1.begin();
    //打印字符串
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	string::iterator it = s1.begin();
    //对字符串进行修改
	while (it != s1.end())
	{
		*it -= 1;
		++it;
	}

    /**********反向迭代器**********/

    string s2("hello world");
	string::reverse_iterator rit = s2.rbegin();
	cout << *rit << " ";
	auto rit = s2.rbegin();//反向迭代器的类型名比较长,我们可以使用auto自动推导类型
	while (rit != s2.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
}

/******************************************************************/

 
void func(const string& s)
{
    /**********const反向迭代器**********/

	string::const_reverse_iterator rit = s.rbegin();
	//auto rit = s.rbegin();
	while (rit != s.rend())
	{
		//*rit -= 1;//不可以修改
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
	
    /**********const迭代器**********/

	string::const_iterator it = s.begin();
	while (it != s.end())
	{
		//*it -= 1;//不可以修改
		cout << *it << " ";
		++it;
	}
}
	
void Test_String3()
{
	const string cstr("hello world");
	func(cstr);
}

int main()
{
	Test_String1();
	Test_String2();
	Test_String3();
	return 0;
}

迭代器遍历的意义是什么呢?

        所有容器都可以使用迭代器这种方式去访问修改;
        对于string,无论是正着遍历,倒着遍历,下标+[]都足够好用,为什么还要迭代器呢?
        对于string,下标+[]都足够好用,确实可以不用迭代器。但是如果其他容器(数据结构)呢?比如:list、map/set(二叉树)是不支持下标遍历的。

        结论:对于string,你得会用迭代器,但是一般我们还是喜欢下标+[]

3.元素的访问——范围for

//范围for,自动往后迭代,自动判断结束
string s1("hello world");
for (auto e : s1)
{
	cout << e << " ";
}
cout << endl;

for (auto& e : s1)//当需要对字符串进行遍历修改是,需要引用
{
	e -= 1;
}

范围for被称为语法糖,简单方便;但是其本质还是被换成了迭代器;

4.string类对象的修改操作

常用函数 说明
operator+= 在字符串后追加一个类对象、常量字符串、单个字符
push_back 只能追加单个字符
append 在字符串后追加一个类对象、常量字符串、单个字符和子字符串等
insert 在指定位置插入字符串、多个字符和类对象
erase 删除指定位置开始向后多个字符、指定位置的字符和某段区间的字符串
swap 交换两个类对象内容
//插入
void Test_String5()
{
	string s("hello world");
	string s1("mlxgyyds");
	//尾插
	s += ' ';				//+=1个空字符
	s += "!!!!";			//+=1个字符串"!!!!"
	s.push_back('c');		//尾插1个字符'c'
	s.append(2, 's');		//尾插2个字符's'
	s.append("www");		//尾插1个字符串"www"
	s.append(s1);			//尾插1个对象s1
	s.append(s1, 4, 4);		//尾插s1字符串从下标4位置开始向后4个字符
	cout << s << endl;

	//头插----效率O(N),尽量少用
	s.insert(0, 1, 'x');	  //在下标0的位置前插入1个字符'x'
	cout << s << endl;

	s.insert(s.begin(), 'q'); //在正向迭代器的起始位置前插入字符'q'
	cout << s << endl;

	s.insert(0, "test ");	  //在下标0的位置前插入字符串"test "
	cout << s << endl;

	//中间位置插入,尽量少用
	s.insert(4, " *****");    //在下标4的位置前插入字符串" *****"
	cout << s << endl;

}

//删除
void Test_String6()
{
	string s("hello world");
	//尽量少用头部和中间的删除,因为要挪动数据,效率低
	cout << s << endl;

	s.erase(0, 1);				//删除下标0位置的字符
	cout << s << endl;

	s.erase(s.size() - 1, 1);	//删除最后一个字符
	cout << s << endl;

	s.erase(3);					//删除下标3(包括3)后面的所有字符
	s.erase(3, 100);			//删除下标3(包括3)后面的所有字符
    s.erase();					//全部删除

    //删除中间
	s.erase(s.begin() + 1, s.end() - 2);//删除区间字符串
	cout << s << endl;

}

int main()
{

	Test_String5();
	Test_String6();
	return 0;
}

5.string类对象的查找操作

常用函数 说明
c_str
返回C格式字符串
find
从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
rfind
从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
substr
在str中从pos位置开始,截取n个字符,然后将其返回
void Test_String4()
{
    string s("hello world");
    cout << s << endl;
    cout << s.c_str() << endl;
    /*虽然都可以实现,但是前者是重载的流插入、流提取操作符,进行打印*/
    /*后者把s识别为char*,进行打印*/

/*****************************一************************************/
    string file("test.txt");
    FILE* fout = fopen(file.c_str(), "w");//当要打开一个文件时,这种函数接口就非常好,适配C语言语法

    //要求你取出文件的后缀
	size_t pos = file.find("."); //找到.的位置
	if (pos != string::npos)
	{
		//string suffix = file.substr(pos, file.size() - pos);

		string suffix = file.substr(pos);//不给npos传值,npos用缺省值,默认从pos位置向后取,有多少取多少
		cout << suffix << endl;
	}

/******************************二***********************************/

	string file("test.txt.zip");
	FILE* fout = fopen(file.c_str(), "w");

	//要求你取出文件的后缀
	size_t pos = file.rfind(".");//rfind倒着找
	if (pos != string::npos)
	{
		//string suffix = file.substr(pos, file.size() - pos);
		string suffix = file.substr(pos);//不给npos传值,npos用缺省值,默认从pos位置向后取,有多少取多少
		cout << suffix << endl;
	}

/********************************三*********************************/

	// 取出url中的域名

	string url("http://www.cplusplus.com/reference/string/string/find/");
	size_t pos1 = url.find(':');//从起始位置向后找':'
	string protocol = url.substr(0, pos1 - 0);//取出协议
	cout << protocol << endl;

	size_t pos2 = url.find('/', pos1 + 3);//从'w'位置向后找'/'
	string domain = url.substr(pos1 + 3, pos2 - (pos1 + 3));//取域名
	cout << domain << endl;

	string uri = url.substr(pos2 + 1);//取剩下的部分
	cout << uri << endl;


}

int main()
{
	Test_String4();
	return 0;
}

6.string类非成员函数重载

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

 relational operators函数重载了==, >=, <=, >, <, != 这些关系运算

string s1("ABCDE"),s2("ABCDF");
if (s1 > s2) 
	cout << s1.c_str() << endl;
else
	cout << s2.c_str() << endl;

//少用
cout << ("hhhhh" < s2) << endl;
cout << (s1 < "hhhhh") << endl;

当你想要获取一个连续的字符串时(含有空格),如果采用cin是无法实现的;此时就需要getline函数。

string s1, s2, s3;
cin >> s1;                   //获取字符串(不能含空格)
cout << s1.c_str() << endl;

getline(cin, s2);            //获取字符串(可以包含空格)
cout << s2.c_str() << endl;

getline(cin, s3, '#');       //获取字符串(可以包含空格,遇到'#'号字符自动结束)
cout << s3.c_str() << endl;

7.string类的其他函数

函数名称 说明
stoi 字符串转 int
stol 字符串转 long
stoul 字符串转 unsigned long
stoll 字符串转 long long
stoull 字符串转 unsigned long long
stof 字符串转 float
stod 字符串转 double
stold 字符串转 long double

to_string

将数值转换为字符串
int val = stoi("1234");//将31.4转换为整数
cout << val << endl;

string str = to_string(3.14);//把31.4转换为字符串
cout << str << endl;

猜你喜欢

转载自blog.csdn.net/sjsjnsjnn/article/details/124903169
今日推荐