C++ -- STL【string的使用】

目录

一、STL介绍

二、C/C++中的字符串

1、C语言中的字符串

2、C++中的字符串

三、string的接口

1、string的迭代器

1.1 begin()与end()函数

1.2 rbegin()与rend()函数

2、string的初始化与销毁

3、string的容量操作

3.1 有效长度与容量大小

3.2 有效长度与容量操作

3.3 string的访问操作

3.3.1 string类对象的三种遍历方式

3.3.2 auto关键字

3.4 string的修改操作

3.4.1 字符串的增加

3.4.2 字符串的替换

3.4.3 字符串的删除

3.4.4 字符串的交换

3.4.5 string的其他操作


一、STL介绍

# STL(standard template libaray-标准模板库):是 C++ 标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。

STL 共有六大组件:


二、C/C++中的字符串

1、C语言中的字符串

# 在 C 语言中,字符串是由字符组成的字符数组,以空字符 '\0' 作为结束标志。由于数组特点,字符串的大小在定义数组时就已经确定,无法更改。

	//数组大小为20
	char str[20] = "hello betty!\n";

# 当然我们可以通过动态内存分配来来解决这个问题,但无疑非常繁琐。

void Test1()
{
	char* str = NULL;
	int len = 0;
	// 初始分配一些内存
	str = (char*)malloc(10 * sizeof(char));
	if (str == NULL) {
		perror("malloc fail");
		return 1;
	}
	strcpy(str, "Hello");
	len = strlen(str);
	// 根据需要扩展字符串
	str = (char*)realloc(str, (len + 6) * sizeof(char));
	if (str == NULL) {
		perror("realloc fail");
		return 1;
	}
	strcat(str, " World");
	printf("%s\n", str);
	//最后释放内存
	free(str);
}

2、C++中的字符串

# 虽然 C++ 兼容 C 语言,在 C++ 中仍然可以使用 C 语言的字符串,但是 C++ 自己实现了一个关于处理字符串的类 —— string ,它提供了许多方便的操作和功能,使得字符串的处理更加安全和高效。下面是一个简单的 string 的使用:

void Test2()
{
	string str = "hello betty!";
	cout << str << endl;
	//改变第一个字符
	str[0]++;
	cout << str << endl;
	//在末尾添加一个字符
	str += 'e';
	cout << str << endl;
	//在末尾添加一个字符串
	str += " hello";
	cout << str << endl;
}

# 相较于 C 语言的字符串,C++ 的字符串明显方便的多。接下来我们将详细介绍 C++ string 类的特点与用法。


三、string的接口

# C++ 为我们提供了丰富的 string 接口,我们可以通过对象来调用,为了方便我们学习我们可以通过查询相关文档辅助 —— string类的接口介绍

1、string的迭代器

迭代器(Iterator)是一种用于遍历容器中元素的工具。它提供了一种统一的方式来访问容器中的元素,而无需关心容器的具体实现细节。对于我们迭代器,我们在使用时将其当做指针使用即可。

# 在 string 类中,我们就可以通过迭代器来访问其具体元素,并且也为我们提供了相应的调用函数。

1.1 begin()与end()函数

begin() 与 end() 函数的使用方法具体如下:

  1. 函数声明:
  • iterator begin();
  • const_iterator begin() const;
  1. 作用:返回指向字符串第一个字符的迭代器。

  2. 返回值:普通对象返回 iterator 迭代器,const 对象返回 const_iterator 迭代器。

  1. 函数声明:
  • iterator end();
  • const_iterator end() const;
  1. 作用:返回指向字符串最后一个字符下一个位置的迭代器。
  2. 返回值:普通对象返回 iterator 迭代器,const 对象返回 const_iterator 迭代器。

#  begin() 、 end() 具体指向情况如下图所示:

# 然后我们可以通过以下代码来演示效果:

void Test3()
{
    string s1 = "hello tata!";
    // 普通迭代器
    string::iterator it = s1.begin(); // 指向第一个位置
    while (it != s1.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
    const string s2 = "hello tata!"; // const对象
    // const 反向迭代器 不能改变字符串中的值
    string::const_iterator itt = s2.begin(); // 指向最后一个字符的下一个位置
    while (itt != s2.end())
    {
        cout << *itt << " ";
        ++itt;
    }
}

1.2 rbegin()与rend()函数

rbegin() 与 rend() 函数的使用方法具体如下:

  1. 函数声明:
  • reverse_iterator rbegin();
  • const_reverse_iterator rbegin() const;
  1. 作用:返回指向字符串最后一个字符位置(即其反向开头)的反向迭代器。
  2. 返回值:普通对象返回 iterator 迭代器,const 对象返回 const_iterator 迭代器。
  1. 函数声明:
  • reverse_iterator rend();
  • const_reverse_iterator rend() const;
  1. 作用:返回指向字符串第一个字符前面一个位置的反向迭代器。
  2. 返回值:普通对象返回 iterator 迭代器,const 对象返回 const_iterator 迭代器。

rbegin() 与 rend() 具体指向情况如下图所示:

# 然后我们可以通过以下代码来演示效果:

void Test4()
{
    string s1 = "hello tata!";
    // 普通反向迭代器
    string::reverse_iterator rit = s1.rbegin(); // 指向最后一个字符位置
    while (rit != s1.rend())
    {
        cout << *rit << " ";
        ++rit;
    }
    cout << endl;
    const string s2 = "hello tata!"; // const对象
    // const 反向迭代器 不能改变字符串中的值
    string::const_reverse_iterator ritt = s1.rbegin(); // 指向最后一个字符的位置
    while (ritt != s1.rend())
    {
        cout << *ritt << " ";
        ++ritt;
    }
}

2、string的初始化与销毁

# 因为 string 是一个类,所以我们在初始化时肯定调用其构造函数初始化。以下就是我们常见初始化的接口:

  1. 第一个使我们的默认构造函数,不需要传参。
  2. 第二个使用的是拷贝构造来初始化。
  3. 第三个是使用一个 string 的某段区间初始化,其中 pos 是字符串下标,npos 是指无符号整数的最大值。
  4. 第四个使用的是某个字符数组初始化。
  5. 第五个使用的是某个字符数组前 n 个字符来初始化
  6. 第六个使用的是 nc 字符初始化。
  7. 第七个使用的是某段迭代器区间初始化。
  8. 最后也能通过赋值运算符重载初始化。

# 下面是具体的代码示例:

void Test5()
{
    // 1. 使用我们的默认构造函数,不需要传参。
    string s1;
    s1 = "hello tata!";
    // 2. 使用的是拷贝构造来初始化。
    string s2(s1);
    // 3. 使用一个string的某段区间初始化,其中pos是字符串下标,npos是指无符号整数的最大值。
    string s3(s2, 1, 7);
    // 4. 使用的是某个字符数组初始化。
    string s4("hello world!");
    // 5. 使用的是某个字符数组前n个字符来初始化
    string s5("hello world!", 5);
    // 6. 使用的是n个c字符初始化。
    string s6(7, 'a');
    // 7. 使用的是某段迭代器区间初始化。
    string s7(s1.begin(), s1.end());
    // 赋值运算符重载初始化
    string s8 = "hello tata!";
    cout << s1 << endl;
    cout << s2 << endl;
    cout << s3 << endl;
    cout << s4 << endl;
    cout << s5 << endl;
    cout << s6 << endl;
    cout << s7 << endl;
    cout << s8 << endl;
}

# 而由于 string 是一个类,出了作用域会自动调用它的析构函数,所以不用显示调用。

# 注意:用另一个 stringpos 位置开始后面的 len 个字符构造时,如果给的长度过大会怎么样呢?会越界吗?

3、string的容量操作

# 接下来我们将学习关于 string 类常见的容量操作:

函数名称功能
size返回字符串的有效长度
length返回字符串的有效长度
capacity返回字符串的容量大小
max_size返回字符串的最大长度
clear清空字符串
empty检查是否为空串,是则返回 ture,否则返回 false
reserve请求改变字符串的容量
resize重新设置有效字符的数量,超过原来有效长度则用 c 字符填充
shrink_to_fit收缩字符串容量

3.1 有效长度与容量大小

# 在 string 类中,我们可以通过 size()length() 返回字符串的有效长度;capacity() 返回字符串的容量,其具体效果如下图:

# 我们也可以通过代码来验证:

void Test6()
{
    string s("hello tata!");
    cout << s.size() << endl;     // 有效长度
    cout << s.length() << endl;   // 有效长度
    cout << s.capacity() << endl; // 容量大小
    cout << s.max_size() << endl; // 最大大小
}

# 其中有效长度 size 以及容量大小 capacity 不包括 \0。而 max_szie 返回字符串最大容量,不同平台下大小可能不一样。而在 vocode 下大小为 4611686018427387903,也就是 INT_MAX 的大小。

# 接下来我们可以来探究一下 string 的扩容机制

void TestCapacity()
{
	string s;
	size_t sz = s.capacity();
	cout << "making s grow:\n";
	for (int i = 0; i < 100; ++i)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

# 在 VS2022 编译器中,string 大概是以 1.5 扩容,但是在 g++ 编译器中却是 2 扩容。所以扩容倍数是不确定的,具体由不同编译器决定。

# 最后我们来谈谈 empty() 函数,它主要用来判断字符串是否为空:

void TestEmpty()
{
	string s1("");//空串
	string s2("hello ");
	if (s1.empty())
	{
		cout << "s1为空串" << endl;
	}
	else
	{
		cout << "s1不为空串" << endl;
	}
	if (s2.empty())
	{
		cout << "s2为空串" << endl;
	}
	else
	{
		cout << "s2不为空串" << endl;
	}
}

3.2 有效长度与容量操作

# 首先我们要介绍的就是 clear() 函数,他能清空字符串,也就是改变有效长度 size ,但不会改变容量 capacity

void TestClear()
{
	string s1("hello world!");
	cout <<"s1的有效长度为:"<< s1.size() << endl;
	cout <<"s1的容量大小为:"<< s1.capacity() << endl;
	s1.clear();
	cout << "s1的有效长度为:" << s1.size() << endl;
	cout << "s1的容量大小为:" << s1.capacity() << endl;
	if (s1.empty())
	{
		cout << "s1是空串" << endl;
	}
}

# 接下来我们将介绍两个可以改变容量的函数 reverseresize。它们之间不小的差别,首先它们的接口如下:

# 我假设字符串原来有效长度为 sz,那么如果 n<szsz<n<capacityn>capasity。两个函数的效果有何不同呢?

void Test8()
{
	string s1("hello world!");
	cout << "reserve测试:" << endl;
	cout << s1 << endl;
	cout << "s1的有效长度为:" << s1.size() << endl;
	cout << "s1的容量大小为:" << s1.capacity() << endl;
	s1.reserve(5);
	cout << s1 << endl;
	cout << "s1的有效长度为:" << s1.size() << endl;
	cout << "s1的容量大小为:" << s1.capacity() << endl;
	s1.reserve(13);
	cout << s1 << endl;
	cout << "s1的有效长度为:" << s1.size() << endl;
	cout << "s1的容量大小为:" << s1.capacity() << endl;
	s1.reserve(25);
	cout << s1 << endl;
	cout << "s1的有效长度为:" << s1.size() << endl;
	cout << "s1的容量大小为:" << s1.capacity() << endl;
	cout << endl;
	cout << "resize测试:" << endl;
	string s2("hello world!");
	cout << s2 << endl;
	cout << "s2的有效长度为:" << s2.size() << endl;
	cout << "s2的容量大小为:" << s2.capacity() << endl;
	s2.resize(5);
	cout << s2 << endl;
	cout << "s2的有效长度为:" << s2.size() << endl;
	cout << "s2的容量大小为:" << s2.capacity() << endl;
	s2.resize(10,'x');
	cout << s2 << endl;
	cout << "s2的有效长度为:" << s2.size() << endl;
	cout << "s2的容量大小为:" << s2.capacity() << endl;
	s2.resize(25, 'x');
	cout << s2 << endl;
	cout << "s2的有效长度为:" << s2.size() << endl;
	cout << "s2的容量大小为:" << s2.capacity() << endl;
}

# 通过上述实验,我们可以总结出以下规律:

  1. n<sz 时,reserve 并不会发生任何改变,resize 会删除有效字符到指定大小。
  2. sz<n<capcity 时,reserve 并不会发生任何改变,resize 会补充有效字符(默认为 \0 )到指定大小。
  3. n>capacity 时,reserve 会发生扩容,resize 会补充有效字符(默认为 \0 )到指定大小。
  • 注意:不同平台下扩容效果可能会不同,但是这个规律是不会改变的。

# 最后我们来介绍一个 C++11 引入的一个可以缩容的函数 shrink_to_fit 它的主要目的就是让有效长度 size 与容量 capasity 适配。

void Test9()
{
	string s1("hello world!");
	cout << s1 << endl;
	cout << "s1的有效长度为:" << s1.size() << endl;
	cout << "s1的容量大小为:" << s1.capacity() << endl;
	s1.reserve(100);//先扩容
	cout << "扩容后s1的有效长度为:" << s1.size() << endl;
	cout << "扩容后s1的容量大小为:" << s1.capacity() << endl;
	s1.shrink_to_fit();//再缩容
	cout << "缩容后s1的有效长度为:" << s1.size() << endl;
	cout << "缩容后s1的容量大小为:" << s1.capacity() << endl;
}

3.3 string的访问操作

# 接下来我们就来介绍 string 常见的访问函数:

函数名称功能
operator [ ]返回指定位置的字符,越界则报错
at返回指定位置的字符,越界则抛异常
back返回字符串最后一个字符(不是 ’\0’ )
front返回字符串第一个字符

# 首先是 operator [ ] 这个运算符重载与 at 函数,它们的功能类似都是返回指定下标字符,并且 char*  类型返回 char*  类型,const char* 类型返回 const char* 类型。

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

# 然后就是 C++11 引入的 frontback 函数,但实用性不是特别大,大家只需要了解。

void Test11()
{
    string s1("hello tata!");
    cout << s1.front() << endl;
    cout << s1.back() << endl;
}

3.3.1 string类对象的三种遍历方式
  • 下标 + [ ]

# 这里因为 string 的底层是字符数组,所以可以用 下标 + [ ] 直接访问到 pos 位置的字符,从而遍历字符串,同时返回的是 pos 位置字符的引用,这样还可以修改 pos 位置的字符。

  • 迭代器:迭代器支持所有容器的访问。

  • 范围 for 遍历

 # 这个范围 for 看起来好像很厉害,可以自动迭代、自动判断结束、自动读取数据、自动推导类型 (自定义类型也可以),但实际底层就是迭代器,汇编转到底层来看,范围 for 和迭代器是一样的。

# 总结:这三种遍历方式都差不多,但是下标 + [ ] 并不是所有的容器都支持,因为可能有些容器空间不连续,但是迭代器和范围 for 所有的容器都支持。

3.3.2 auto关键字

# auto 关键字:在这里补充2个 C++11 的小语法,方便我们后面的学习。

# 在早期 C/C++ 中 auto 的含义是:使用 auto 修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。C++11 中,标准委员会变废为宝赋予了 auto 全新的含义即:auto 不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto 声明的变量必须由编译器在编译时期推导而得。

# 用 auto 声明指针类型时,用 autoauto* 没有任何区别,但用 auto 声明引用类型时则必须加 &,当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

# 注意:

auto 不能作为函数的参数,可以做返回值,但是建议谨慎使用。
auto 不能直接用来声明数组。

auto 的价值就是自动推导类型,如果类型过长就很方便,但是一定程度牺牲了可读性。

3.4 string的修改操作

string 关于修改的函数的接口都比较多,一一列举比较麻烦,这里我们只重点介绍常用的接口,剩下的大家具体使用时查官方文档即可。下面是常见的关于 string 修改的函数接口:

函数名称功能
push_back在字符串后追加字符
operator+=在字符串后追加字符或字符串
append在字符串后追加字符串
insert在指定位置追加字符或者字符串
assign使用指定字符串替换原字符串
replace用新字符串替换原字符串指定区间
pop_back删除字符串最后一个字符
erase删除字符串指定部分
swap交换两个字符串
3.4.1 字符串的增加

# 首先我们来介绍字符串的增加操作,在末尾添加字符我们可以使用 push_back,在末尾添加字符串我们可以使用 append,而 operator+= 既可以在末尾添加字符,也可以添加字符串,insert 可以在任意位置追加字符或者字符串。

void Test12()
{
    string s("hello tata!");
    // 追加一个!
    s.push_back('!');
    cout << s << endl;
    s += '!';
    cout << s << endl;
    s.insert(0, 1, '!');
    cout << s << endl;
    // 追加一个字符串
    s.append("hello ");
    cout << s << endl;
    s += "world!";
    cout << s << endl;
    // 在下标0处追加字符串
    s.insert(0, "hello ");
    cout << s << endl;
}

# 但注意要谨慎使用 insert 因为在头部或中间位置插入必然要挪动数据。大量使用会降低程序效率。

# 当然 append 与 insert 的接口不止这些,下面是具体的的接口,需要时直接查文档即可。

3.4.2 字符串的替换

# 接下来我们来介绍两个字符串替换的函数 assign 以及 replace,其中 assign 是直接替换掉原字符串,而 replace 是替换原字符串的某段区间。

void Test13()
{
	string s1("hello world!");
	string s2;
	//直接用s1替换s2
	s2.assign(s1);
	cout << s2 << endl;
	//用s1的某段区间替换s2(第三个参数省略,默认为string::npos)
	s2.assign(s1, 6);
	cout << s2 << endl;
	string s3("i am tata!");
	//用s1替换掉2下标长度为2的区间
	s3.replace(2, 2, s1);
	cout << s3 << endl;
	//用字符数组前n个字符替换
	s3.replace(0, 2, "hhhh", 2);
	cout << s3 << endl;
}

# replace 也会挪动数据所以也不要大量使用。

# 当然 assign 与 replace 的接口不止这些,下面是具体的的接口,需要时直接查文档即可。

3.4.3 字符串的删除

# 字符串的删除有支持删除最后一个字符的 pop_back,也有支持删除任意区间的 erase

void Test14()
{
	string s("hello tata!");
	cout << s << endl;
	//删除最后一个字符
	s.pop_back();
	cout << s << endl;
	//删除迭代器所指字符
	s.erase(s.begin());
	cout << s << endl;
	//删除0下标长度为3的一段区间
	s.erase(0, 3);
	cout << s << endl;
	//删除一段迭代器区间
	s.erase(s.begin(), s.end() - 2);
	cout << s << endl;
}

# 注意 erase 也需要谨慎使用,因为删除也要挪动数据。

3.4.4 字符串的交换

# 最后我们来介绍一个字符串的交换函数 swap,这个 swap 函数与算法库中的 swap 函数并不相同,算法库中的 swap 函数将 string 中每个具体值都交换。而 string 中的 swap 函数实现的是指针交换,效率明显高的多。

void TestSwap()
{
	string s("hello tata!");
	string p("hello world!");
	cout << s << endl;
	cout << p << endl;
	s.swap(p);
	cout << s << endl;
	cout << p << endl;
}

3.4.5 string的其他操作

# 除了上面操作外,string 还有一些格外补充操作,这里值挑几个常用的函数为大家介绍。·

函数名称功能
c_str返回 C 格式的字符串
substr从字符串 pos 位置开始,截取 n 个字符,然后将其返回
find从字符串 pos 位置开始往后找字符 c,返回该字符在字符串中的位置
rfind从字符串 pos 位置开始往前找字符 c,返回该字符在字符串中的位置
find_first_of在原字符串中,从前往后找匹配串中第一个匹配的字符
find_last_of在原字符串中,从后往前找匹配串中第一个匹配的字符
find_first_not_of在原字符串中,从前往后找匹配串中第一个不匹配的字符
find_last_not_of在原字符串中,从后往前找匹配串中第一个不匹配的字符
getline获取一行字符串
operator<<流提取重载
operator>>流插入重载

# 首先是 substr find 函数,这两个函数可以结合使用。

void Test15()
{
	string s("hello tata!");
	// 在字符串s中寻找b
	size_t pos = s.find('t');
	// 从下标6开始截取长度为6的字符串
	string str = s.substr(pos, 6);
	cout << str << endl;
}

# 需要注意的是如果 find 不存在查找目标,那就返回 npos

find_first_offind_last_offind_first_not_of find_last_not_of 的用法非常类似,我们就只以其中一种举例:

void Test16()
{
	string str("Please, replace the vowels in this sentence by asterisks.");
	//从str字符串中任意匹配aeiou
	size_t found = str.find_first_of("aeiou");
	//找不到返回npos
	while (found != string::npos)
	{
		str[found] = '*';
		//从下一个位置找
		found = str.find_first_of("aeiou", found + 1);
	}
	cout << str << endl;
}

# 这里是 find 与 find_last_of 详细的接口,大家可以根据实际需求参考使用。

# 最后我们来介绍以下 getline,它与流插入都是读取字符串,但是区别就是 getline 遇见空格不会停止,而流插入会停止。这区别和 C 语言中 scanf gets 类似

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值