前言

之前我们介绍了类和对象(四个常用的默认成员函数)、STL概述(包罗数据结构和算法的软件框架)、编码表(UTF-8、ASCII、GBK)和两个现现代C++语法auto关键字和范围for循环。那么本篇博主就带大家探索第一个STL容器——string。

目录

前言

(一)为什么学习string类

1.c语言中字符串的弊端

2.两个面试题

(二)标准库里的string类

1.string - C++ Reference

2.auto关键字和范围for

简单介绍

3.string类的常用接口说明

1.Member functions(成员函数)

​编辑

默认构造、拷贝构造:

赋值运算符重载:

2.Iterators(迭代器)

定义

格式

const对象如何使用迭代器

3.Capacity(容量相关接口)

1.size

2.resize

3.reserve

4.clear

5.empty

4.Element access(元素访问)

1.operator [ ]

2.at

3.使用迭代器实现元素遍历和修改

(三)本文完整代码

小结


(一)为什么学习string类

1.c语言中字符串的弊端

C语言中字符串是以‘\0’结尾的一些字符的集合,为了操作方便,C语言库中提供了一些str系列的库函数,但是这些库函数和字符串还是分离的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不注意可能就会引起越界访问。

注释:OOP(Object-Oriented Programming)指面向对象编程。

2.两个面试题

在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。

例:

1.把字符串转换成整数_牛客题霸_牛客网

2.415. 字符串相加 - 力扣(LeetCode)

PS:这两道题目会在后续string类的题目总结中分享,请读者持续关注!


(二)标准库里的string类

1.string - C++ Reference

该标题为string类的文档介绍,我们无论是在以后的C++学习中还是在未来工作中都必须具备独立看文档的能力,大多数权威网站都是英文的,大家务必按住性子逐字逐句分析语法语义。

在前期我们可以借助一些翻译软件,如:有道词典、彩云小译,实在不行也可以借助大模型(浏览器自带的翻译实在是太垃圾了)但尽量自己看文档,因为STL设计接口名字时都不会乱起名,基本上都是相通的,有些接口的参数设置也是有规律的,有一定经验后基本不需要翻译软件就能看懂个七七八八。

以string类为例,对比一下浏览器自带翻译和彩云小译:

彩云小译  译为:

浏览器自带翻译  译为:

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

2.auto关键字和范围for

简单介绍

auto 让编译器根据初始化表达式自动推导变量的类型。

范围for是基于范围的 for 循环用于遍历容器或数组,其内部原理是编译器将其展开为基于迭代器的普通循环。自动取容器数据赋值,自动迭代++,自动判断结束。

 详细介绍见上篇最后一部分:

【C++初阶】STL 开篇:站在巨人肩膀上,先聊聊编码和现代语法-CSDN博客

3.string类的常用接口说明

在文档中我们可以看出它把string类的接口分为了以下几类:

1.Member functions(成员函数)

2.Iterators(迭代器)

3.Capacity(容量相关接口)

4.Element access(元素访问)

5.Modifiers(修改字符串内容的操作)

6.String operations(字符串运算)

1.Member functions(成员函数)

默认构造、拷贝构造:
string::string - C++ Reference函数名称功能说明
string()构造空的string对象,即空字符串
string(const char* s)用C-string来构造string类对象
string(size_t n,cahr c)

string 类独享中包含n个字符c

string(const string&s)拷贝构造函数

使用方式:

void Test_String()
{
    string s1;
    string s2("abcde");
    string s3(4,'x');
    string s4(s3);
}

运行结果:

赋值运算符重载:
void Test(){
    string s1="aaabbb";
}

2.Iterators(迭代器)

定义

迭代器是一种统一访问容器元素的方式,它像一个“通用的指针”,让你可以遍历容器(如 vector、string、list 等)中的元素,而无需关心容器的内部结构。

格式
string::iterator it=str.begin();

begin():指向字符串第一位有效字符。

end():指向'\0'的位置。

rbegin():从后往前遍历,指向字符串最后一位有效字符('\0'前一位)。

rend():指向字符串第一位有效字符的前一个内存单元。

const对象如何使用迭代器
const string s2="hello world";
string::const_iterator it2 = s2.begin();

注意:

首先,s2 是一个 const string 对象,表示其内容不可修改。

其次,对于 const 对象,调用 begin() 会返回 const_iterator(而不是普通的 iterator)。const_iterator 只能读取元素,不能修改。

最后,string::const_iterator it2 = s2.begin(); 用返回的 const_iterator 初始化 it2,类型匹配,合法。

3.Capacity(容量相关接口)

1.size

size和 length的功能在string类里一致,是因为在STL出现前string类已被开发,string类使用的是length,但STL统一规范的大小是用size表示。size为通用接口,所以这里仅介绍size。

int main()
{

	string s1("abcde");
	cout << s1.size() << endl;//输出5
	return 0;
}

说明:size大小不包括' \0',仅表示有效字符的个数。

2.resize

int main()
{

	string s1("abcde");
	string s2(s1);
	cout << s1.size() << endl;
	s1.resize(2);
	cout << s1 << endl;
	s2.resize(10, 'x');
	cout << s2 << endl;
	return 0;
}

运行结果

3.reserve

接下来博主将会使用vs和gcc两种编译器向你展示不同编译器是如何处理reserve请求的容量变更的。

vs环境下:

void Test_String4()
{
	string s1("hello world");
	cout << s1.capacity() << endl;
	s1.reserve(20);//变更大
	cout << s1.capacity() << endl;
	s1.reserve(14);//不做修改
	cout << s1.capacity() << endl;
	
}

运行结果

linux环境下:

总结:如果用reserve缩小容量,vs直接无视缩容指令,而gcc会酌情缩容,但一定会保证容量大小大于size。

作用:若确定知道需要多少大小,可以直接reserve,一步到位,避免多次扩容,提高效率。

4.clear

擦除字符串的内容,使其成为空字符串 (长度为 0 个字符)。

void Test_String4()
{
	string s1("hello world");
	cout << s1.size() << endl;  // 11
	cout << s1.capacity() << endl;  // 15
	s1.clear();
	cout << s1.size() << endl;  // 0
	cout << s1.capacity() << endl;  // 15
}

通过输出结果可以看出:clear会使size清零,但不会影响capacity。

5.empty

返回字符串是否为空 (即其长度是否为 0)。

4.Element access(元素访问)

1.operator [ ]
void Test_String2()
{
	//[]运算符重载
	string s1 = "abcde";
	cout << s1 << endl;
	s1[0] = 'x';
	cout << s1 << endl;  //xbcde
	//s1[6];   //越界访问, 触发断言终止
    //遍历,修改
    for (size_t i = 0;i < s1.size();i++)
    {
    	s1[i]++;
    }
    cout << s1 << endl;  //ycdef
}

意义:使对象像数组一样使用,string、vector底层都是数组。C语言数组的月结检查是一种抽查,在数组的结束位置设置几个标志位,C++在operator [ ]实现时加上了断言,对内存隐私更有保障。

2.at

与operator [ ]相比,at越界访问的表现更温和,抛异常但不会强制终止程序。

void Test_String3()
{
	string s1 = "abcde";
	cout << s1 << endl;
	s1[0] = 'x';
	cout << s1 << endl;
	//s1[6];   //触发断言终止
	//at 接口: 抛异常
	s1.at(6) = 'y';
	cout << s1 << endl;
}
int main()
{
	try
	{
		Test_String3();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
}

运行结果:

3.使用迭代器实现元素遍历和修改
//迭代器:行为类似于指针
//正向遍历,修改
//string::iterator it1 = s1.begin();
auto it1 = s1.begin();
for (size_t i = 0;it1 != s1.end();i++)
{
	(*it1)--;
	it1++;
}
cout << s1 << endl;

(三)本文完整代码

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
#include<list>
#include<algorithm>
using namespace std;
void Test_String1()
{
	//1
	string s1 = "come on";//拷贝初始化
	string s2("come on");//直接初始化,与s1效果一样
	//3
	string s3(s2);
	//4  string (const string& str, size_t pos, size_t len = npos);
	string s4(s2, 0);
	string s5(s4, 2, 5);
	//5  string (const char* s, size_t n);
	string s6("I love C++", 6);
	cout << s1 << endl << s2 << endl << s3 << endl << s4 << endl << s5 << endl << s6;
}
void Test_String2()
{
	//[]运算符重载
	string s1 = "abcde";
	cout << s1 << endl;
	s1[0] = 'x';
	cout << s1 << endl;
	//s1[6];   //触发断言终止
	//at 接口: 抛异常
	s1.at(1) = 'y';
	cout << s1 << endl;
	//遍历,修改
	for (size_t i = 0;i < s1.size();i++)
	{
		s1[i]++;
	}
	cout << s1 << endl;
	//迭代器:行为类似于指针
	//正向遍历,修改
	//string::iterator it1 = s1.begin();
	auto it1 = s1.begin();
	for (size_t i = 0;it1 != s1.end();i++)
	{
		(*it1)--;
		it1++;
	}
	cout << s1 << endl;
	//const对象调用迭代器
	const string s2="hello world";
	string::const_iterator it2 = s2.begin();
	//const反向迭代
	string::const_reverse_iterator tit2 = s2.rbegin();


	list<int> lit;
	lit.push_back(1);
	lit.push_back(2);
	lit.push_back(3);
	string::iterator ret = find(s1.begin(), s1.end(), 'x');
		if (ret != s1.end())
		{
			cout << "找到x" << endl;
		}
	list<int>::iterator ret2 = find(lit.begin(), lit.end(), 2);
	//char* ret3= find(s1.begin(), s1.end(), 'x');
	// 引用
	int i = 0;
	int& r1 = i;
	// r2不是int&引用,是int
	auto r2 = r1;
	//r3是int& 引用
	auto& r3 = r1;
	cout << &r2 << endl;
	cout << &r1 << endl;
	cout << &i << endl;
	cout << &r3 << endl;
}
void Test_String3()
{
	//string s1 = "引用";
	//s1[0]++;
	//cout << s1 << endl;
	string s1 = "abcde";
	cout << s1 << endl;
	s1[0] = 'x';
	cout << s1 << endl;
	//s1[6];   //触发断言终止
	//at 接口: 抛异常
	s1.at(6) = 'y';
	cout << s1 << endl;
}
void Test_String4()
{
	string s1("hello world");
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	s1.clear();
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	//s1.reserve(20);
	//cout << s1.capacity() << endl;
	//s1.reserve(14);
	//cout << s1.capacity() << endl;
}
void Test_String5()
{
	//[]运算符重载
	string s1 = "abcde";
	cout << s1 << endl;
	s1[0] = 'x';
	cout << s1 << endl;
	//s1[6];   //越界访问, 触发断言终止
	//遍历,修改
	for (size_t i = 0;i < s1.size();i++)
	{
		s1[i]++;
	}
	cout << s1 << endl;
}
int main()
{
	try
	{
		Test_String3();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	/*Test_String3();*/
	//string s1("abcde");
	//string s2(s1);
	//cout << s1.size() << endl;
	//s1.resize(1);
	//cout << s1 << endl;
	//s2.resize(10, 'x');
	//cout<<s2<<endl;
	//Test_String5();
	return 0;
}

小结

本篇作为 std::string 的入门篇,我们从 C 语言字符串的弊端出发,明确了学习 string 类的必要性,并给出了两道经典的面试题作为引子。

接着,我们简要回顾了 auto 关键字和范围 for 的基本原理。

在 string 类的常用接口部分,我们重点学习了:

成员函数:默认构造、拷贝构造、赋值运算符重载,掌握了如何创建和复制字符串对象。

迭代器:理解了迭代器作为“通用指针”的作用,知道了 begin()/end() 的用法,并区分了 const_iterator 与普通迭代器的使用场景。

容量相关接口:size() 获取字节数,resize() 改变有效字符个数,reserve() 预分配内存以避免频繁扩容,clear() 清空内容,empty() 判空。

元素访问:operator[] 和 at() 的区别(后者边界检查),以及如何利用迭代器遍历和修改字符串。

由于篇幅有限,string 的 修改器(Modifiers)(如 append、insert、erase、replace、push_back、pop_back 等)以及 字符串操作(String operations)(如 c_str、find、rfind、substr 等)尚未展开。这些内容将在下一篇博客中详细讲解,并会结合大量示例展示如何高效地操作字符串。

创作不易,如果你觉得有帮助,欢迎点赞、收藏、评论。我们下篇见!

更多推荐