目录

STL介绍

string介绍

string构造

string遍历

求string字符个数

求string容量

string 扩容

string 改变元素个数

判断 string 是否为空

清空 string

将 string 容量缩小到 size

string 尾插

string 赋值

string 任意位置插入

string 任意位置删除

string 替换

交换两个string对象

string 查找

获取底层C风格字符串起始地址

关系运算符重载

字符串读入问题


STL介绍

C++ STL(Standard Template Library,标准模板库)是 C++ 标准库的核心组成部分,提供了通用数据结构(容器)、算法迭代器函数对象等组件,旨在提高开发效率、代码复用性和性能。其设计核心是泛型编程(模板),支持跨数据类型的通用操作

string介绍

从严格的历史定义上讲,std::string 不是原始 STL 的一部分,设计和实现要早于 STL 的广泛采用,但在今天的 C++ 中,std::string 是 C++ 标准库的一个核心组件,并且因为它本身是一个模板类,与 STL 的设计哲学和风格完全一致,并且我们总是把它和 STL 的容器、算法一起使用,所以通常被视为 STL/标准库大家庭的一员

注意:我们本篇博客要介绍的 string 并不是原始类模板,而是经过 typedef 过的:

C++ 为了支持不同字符集(如普通字符 char、宽字符 wchar_t、UTF-8 字符 char8_t 等),设计了 模板类 std::basic_string,适配不同字符编码场景,所以我们平时写 std::string 时,本质就是在使用 std::basic_string<char>

string构造

#include<iostream>
using namespace std;
int main()
{
	string s1; //没有显示初始化默认是空字符串
	string s2("hello world"); //带参数创建
	string s3 = "hello world"; //单参数的构造函数支持隐式类型转化
	string s4(s3); //s3对象完全拷贝给s4
	cout << s4 << endl; //hello world
	string s5(s3, 1, 3); //从s3下标为1的位置开始取3个字符拷贝给s5
	cout << s5 << endl; //ell
	string s6(s3, 1, 15); //15>字符串长度, 从1开始取整个字符串
	cout << s6 << endl; //ello world
	string s7(s3, 1); //第三个缺省参数默认是-1(size_t),因此取完整个字符串
	cout << s7 << endl; //ello world;
	
	//迭代器区间拷贝构造初始化, 左闭右开
	string s8(s3.begin(), s3.end()); //s1.begin()获取的是字符串首位置,s1.end()获取的是字符串最后一个有效字符下一个位置('\0'的位置)
	cout << s8 << endl;  //hello world
	string s9(++s3.begin(), --s3.end());
	cout << s9 << endl; //ello worl
}

string遍历

方式一:operator[ ]重载

#include<iostream>
using namespace std;
int main()
{
	string s1 = "hello world";
	
	//读
	for (size_t i = 0; i < s1.size(); i++) //size()是用来获取字符串中有效字符个数(不含'\0')
	{
		cout << s1[i] << " "; //h e l l o   w o r l d
	}
	cout << endl;

	//写
	for (size_t i = 0; i < s1.size(); i++)
	{
		s1[i]++;
		cout << s1[i] << " "; //i f m m p ! x p s m e
	}
}

方式二:迭代器

迭代器提供了一种遍历容器中元素的方法,我们无需关心容器内部的实现细节

1.正向迭代器:

#include<iostream>
using namespace std;
int main()
{
	string s1 = "hello world";
	//iterator是迭代器的意思, 封装在string内部
	string::iterator it = s1.begin(); //s1.begin()返回的是字符串起始位置
	//it是迭代器类型,但是我们可以类似认为是是指针变量, 按照指针的方式去访问和操作即可
	while (it != s1.end())
	{
		cout << *it << " "; //类似于指针解引用
		it++; //类似于指针向后移动
	}
	cout << endl;
}

2.反向迭代器

除了正向迭代器之外,还有反向迭代器,借助反向迭代器,可以实现逆序遍历

#include<iostream>
using namespace std;
int main()
{
	string s1 = "hello world";
	string::reverse_iterator rit = s1.rbegin();
	while (rit != s1.rend())
	{
		cout << *rit << " ";
		rit++;
	}
	cout << endl;
}

3.const正向迭代器

若string对象被const修饰了,此时要正向遍历必须用const正向迭代器,否则权限放大会报错

#include<iostream>
using namespace std;
void func(const string& s)
{
	string::const_iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
}
int main()
{
	string s1 = "hello world";
	func(s1);
}

4.const反向迭代器

#include<iostream>
using namespace std;
void func(const string& s)
{
	string::const_reverse_iterator rit = s.rbegin(); 
	//上一行代码太冗余了,此时auto的价值就体现出来了
	//auto rit = s.rbegin();
	while (rit != s.rend())
	{
		cout << *rit << " ";
		rit++;
	}
	cout << endl;
}
int main()
{
	string s1 = "hello world";
	func(s1);
}

方式三:范围for

范围for的底层其实是借助迭代器实现的,只是上层使用起来更加方便

#include<iostream>
using namespace std;
int main()
{
	string s1 = "hello world";
	for (auto ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
}

方式四:at()方法

at方法和operator[ ]无本质区别,都是访问特定位置的元素,唯一区别是 at 会有严格的越界检查,当访问的位置超过 string范围,会直接抛出 std::out_of_range 异常

#include<iostream>
using namespace std;
int main()
{
	string s1 = "hello world";
	for (size_t i = 0;i < s1.size();i++)
	{
		cout << s1.at(i) << " ";
	}
	cout << endl;
}

求string字符个数

size / length 都可以求字符串已经存储的有效字符的个数,注意不包含 '\0',length 在实际几乎不用, 因为其他STL容器统一提供的都是size接口 

#include<iostream>
using namespace std;
int main()
{
	string s1 = "hello world";
	cout << s1.size() << endl; //11 
	cout << s1.length() << endl; //11
}

求string容量

capacity用来计算当前字符串的容量,返回字符串已分配内存的容量(字符数),同样的一个字符串,不同编译器下给定的capacity的大小可能会不太一样,没有规律

#include<iostream>
using namespace std;
int main()
{
	string s1 = "hello world";
	cout << s1.capacity() << endl; //15

	string s2 = "come up";
	cout << s2.capacity() << endl;  //15

	string s3 = "weclome to beijing";
	cout << s3.capacity() << endl; //31
}

string 扩容

只操作 capacity,不改变 size,不初始化新增的内存空间,目的是避免频繁扩容:比如提前知道要存 1000 个元素,先用 reserve(1000) 预分配内存,后续 push_back 时就不用多次重新分配内存,提高效率!

注意:传入的参数是我们期望申请的容量大小,而实际编译器分配的大小都是大于我们申请的大小

#include<iostream>
using namespace std;
int main()
{
	string s1 = "hello world";
	cout << s1.capacity() << endl; //15
	s1.reserve(20);
	cout << s1.capacity() << endl; //31
}

string 扩容的规律

#include<iostream>
using namespace std;
int main()
{
	string s;
	s.reserve(20);
	size_t old = s.capacity();
	for (size_t i = 0;i < 1000;i++)
	{
		s.push_back(i);
		if (old != s.capacity())
		{
			cout << "扩容到:" << s.capacity() << endl;
			old = s.capacity();
		}
	}
}

vs2022下测试:每次扩容到的空间大小大概是上一次空间大小的 1.5倍

Linux-ubuntu下测试: 每次扩容到的空间大小是上一次空间大小的 2 倍

string 改变元素个数

resize是将元素的 size 调整为 n,而根据 n 与 原先 size 的大小关系,有以下几种情况:

①n < size

相当于尾删元素,size减小到n,capacity不变

#include<iostream>
using namespace std;
int main()
{
	//n < size
	string s1 = "hello world";
	cout << s1.size() << endl; //11
	cout << s1.capacity() << endl; //15
	s1.resize(5);
	cout << s1<< endl; //hello
	cout << s1.size() << endl; //5
	cout << s1.capacity() << endl; //15
}

②n >= size && n <= capacity

相当于尾插元素,size增加到n,capacity不变, resize()给定了第二个字符参数,就插入该字符,否则默认插入'\0'

#include<iostream>
using namespace std;
int main()
{
	//n >= size && n <capacity
	//resize不传字符参数
	string s1 = "hello world";
	cout << s1.size() << endl; //11
	cout << s1.capacity() << endl; //15
	s1.resize(13); //插入两个'\0'
	cout << s1 << endl; //hello world
	cout << s1.size() << endl; //13
	cout << s1.capacity() << endl; //15

	//resize()传字符参数
	string s1 = "hello world";
	s1.resize(13, 'x');
	cout << s1 << endl; //hello worldxx
}

③ n > capacity

相当于插入元素,size增加到n,capacity增大(编译器决定增大到多少)

#include<iostream>
using namespace std;
int main()
{
	string s1 = "hello world";
	cout << s1.capacity() << endl; //15
	s1.resize(20, 'x');
	cout << s1 << endl; //hello worldxxxxxxxxx
	cout << s1.capacity() << endl; //31
}

判断 string 是否为空

empty 用于判断字符串是否为空,为空返回真,不为空返回假

#include<iostream>
using namespace std;
int main()
{
	string s1 = "hello world";
	string s2 = "";
	cout << s1.empty() << endl; //0
	cout << s2.empty() << endl; //1
}

清空 string

clear用于清空字符串,使得字符串中有效字符的个数为0,因此size为0,capacity不变

#include<iostream>
using namespace std;
int main()
{
	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
}

将 string 容量缩小到 size

shrink_to_fit 是用来向系统请求把容量capacity减小到和有效字符个数size一样大,注意,该函数是一个非强制性的请求,它建议字符串释放未使用的内存,但不保证容量一定会精确等于大小。

#include<iostream>
using namespace std;
int main()
{
	string s1;
	s1 = "hello world";
	s1.reserve(100);
	cout << s1.size() << endl; //11
	cout << s1.capacity() << endl; //111
	s1.shrink_to_fit();
	cout << s1.size() << endl; //11
	cout << s1.capacity() << endl; //15
}

string 尾插

1. push_back 用于尾插单个字符

#include<iostream>
using namespace std;
int main()
{
	string s;
	s.push_back('h');
	s.push_back('e');
	s.push_back('l');
	cout << s << endl; //hel
}

2. append 可以追加(尾插) 整个字符串、字符串的一部分、若干个字符等

#include<iostream>
using namespace std;
int main()
{
	string s1 = "hello ";
	string s2 = "world";
	s1.append(s2);
	cout << s1 << endl; //hello world

	string s3 = "hello ";
	string s4 = "world";
	s3.append(s4, 0, 2); //尾插s2从下标0开始的2个字符
	cout << s3 << endl; //hello wo

	string s5 = "hello ";
	s5.append("world"); 
	cout << s5 << endl; //	hello world

	string s6 = "hello ";
	s6.append("world", 3); //尾插world的前三个字符到hello
	cout << s6 << endl; //	hello wor

	string s7 = "hello ";
	s7.append(2, 'w'); //追加2个'w'字符在s3后面
	cout << s7 << endl; //hello ww
}

3. += 本质也是一种尾差字符串的方式,尾插字符串或者字符到string对象末尾

#include<iostream>
using namespace std;
int main()
{
	//+=字符
	string s1 = "hello ";
	s1 += 'w';
	cout << s1 << endl; //hello w

	//+=字符串
	string s2 = "hello ";
	s2 += "world";
	cout << s2 << endl; //hello world

	string s3 = "hello ";
	string s4 = "world";
	s3 += s4;
	cout << s3 << endl; //hello world
}

string 赋值

#include<iostream>
using namespace std;
#include<string>
int main()
{
	string s1("hello world");
	string s2("hahaha");
	s1 = s2;
	cout << s1 << endl; //hahaha
	s1 = "hehehe";
	cout << s1 << endl; //hehehe
	s1 = 'w';
	cout << s1 << endl; //w
}

assign 的作用是将 完整字符串 / 字符串的一部分 / 若干字符 赋值给 string 对象

#include<iostream>
using namespace std;
int main()
{
	//赋值 string对象
	string s1 = "hello";
	string s2 = "world";
	s1.assign(s2); //将s2赋值给s1
	cout << s1 << endl; //world

	//赋值 常量字符串
	string s3 = "hello";
	s3.assign("world"); //将"world"赋值给s3
	cout << s3 << endl; //world

	//赋值 常量字符串的前n个字符
	string s4 = "hello";
	s4.assign("world", 2); //将"world"的前2个字符赋值给s4
	cout << s4 << endl; //wo

	//赋值 n个字符
	string s5 = "hello ";
	s5.assign(2, 'w'); //赋值2个'w'字符给s5
	cout << s5 << endl; //ww

	//赋值 string的子串
	string s6 = "hello";
	string s7 = "handsome boy";
	s6.assign(s7, 3, 5); //赋值s7从下标为3的位置开始的5个字符给s6
	cout << s6 << endl; //dsome

	string s8 = "hello";
	string s9 = "world";
	s8.assign(s9, 2); //赋值s9从下标为2开始的子串赋值给s8
	cout << s8 << endl; //rld
}

string 任意位置插入

 insert 可以在指定位置插入字符或者字符串

①插入单个字符

#include<iostream>
using namespace std;
int main()
{
	//插入单个字符
	string s1 = "hello world";
	s1.insert(2, 3, '*'); //下标为2的位置插入3个'*'
	cout << s1 << endl; //he*** llo world

	string s2 = "hello world";
	s2.insert(s2.begin(), 'w'); //开始位置插入字符'w'
	cout << s2 << endl; //whello world

	string s3 = "hello world";
	s3.insert(s3.begin(), 2, 'w'); //开始位置插入2个字符'w'
	cout << s3 << endl; //wwhello world
}

②插入字符串

#include<iostream>
using namespace std;
int main()
{
	//插入字符串
	//插入整个字符串
	string s1 = "hello world";
	s1.insert(2, "come"); //下标为2位置开始插入字符串"come"
	cout << s1 << endl; //hecomello world

	//插入字符串的一部分
	string s2 = "hello world";
	string s3 = "Never give up";
	s2.insert(2, s3, 2, 6); //取s3字符串下标从2开始的6个字符插入s2下标为2的位置
	cout << s2 << endl; //hever gillo world

	string s4 = "hello world";
	s4.insert(2, "world", 2); //取world下标为0(默认)开始的2个字符插入到s4中下标为2的位置
	cout << s4 << endl; //hewollo world
}

string 任意位置删除

 erase 用于删除指定位置开始的若干个字符

#include<iostream>
using namespace std;
int main()
{
	string s1 = "hello world";
	s1.erase(2, 5); //删除s1下标从2开始的5个字符
	cout << s1 << endl; //heorld

	string s2 = "hello world";
	s2.erase(7); //删除s1下标从7开始的剩余字符
	cout << s2 << endl; //hello w

	string s3 = "hello world";
	s3.erase(); //删除整个字符串
	cout << s3 << endl; //空
}

string 替换

 replace是用新的字符或者字符串替换原字符串部分字符

#include<iostream>
using namespace std;
int main()
{
	string s1 = "hello world";
	s1.replace(2, 5, "xxxxxxxxxxxx"); //将s1从下标2开始的5个字符替换成"xxxxxxxxxxxx"
	cout << s1 << endl; //hexxxxxxxxxxxxorld

	string s2 = "hello world";
	s2.replace(++s2.begin(), --s2.end(), "xxxxxx"); //将s1第2个字符的位置到倒数第二个字符位置的子串替换成"xxxxxx"
	cout << s2 << endl; //hxxxxxxd

	string s3 = "hello world";
	s3.replace(2, 5, "xxx***&&&", 3, 4); //将s3下标从2开始的5个字符替换成"xxx***&&&"下标从3开始的4个字符
	cout << s3 << endl; //he***&orld

	string s4 = "hello world";
	s4.replace(2, 5, 3, '*'); //将s4下标从2开始的5个字符替换成3个'*'
	cout << s4 << endl; //he***orld

	string s5 = "hello world";
	s5.replace(2, 5, "&*@!", 4); //将s5下标从2开始的5个字符替换成"&*@!"的前两个字符
	cout << s5 << endl; //he&*orld
}

交换两个string对象

#include<iostream>
#include<string>
using namespace std;
int main()
{
	string s1("hello");
	string s2("world");
	s1.swap(s2);
	cout << s1 << endl; //world
	cout << s2 << endl; //hello
}

注意:除了string的成员函数提供了 swap接口,还存在全局的 std::string 接口,也可以完成两个 string的交换

#include<iostream>
using namespace std;
int main()
{
	string s1("hello");
	string s2("world");
	std::swap(s1, s2);
	cout << s1 << endl; //world
	cout << s2 << endl; //hello
}

string 查找

①find()

#include<iostream>
using namespace std;
int main()
{
	//find会返回找到的字符串的首字符或者字符的下标
	string s1 = "hello world";
	size_t pos1 = s1.find("llo", 1); //从s1下标为2开始查找字符串"llo"
	cout << pos1 << endl; //2
	size_t pos2 = s1.find("llx", 1);
	cout << pos2 << endl; //找不到返回npos(size_t类型,值为-1,是一个很大的整数)

	string s2 = "hello world";
	size_t pos3 = s1.find('l'); //默认从s2的第一个位置开始找'l'字符首次出现的位置
	cout << pos3 << endl; //2
}

 ②rfind()

 find()是从左到右查找,而rfind()是从右到左查找

#include<iostream>
using namespace std;
int main()
{
	//find会返回找到的字符串的首字符或者字符的下标
	string s1 = "hello world";
	size_t pos1 = s1.rfind("wor", -3); //从s1倒数第3个位置开始查找"wor"
	cout << pos1 << endl; //6

	string s2 = "hello world";
	size_t pos3 = s1.rfind('l'); //默认从s2的倒数第一个位置开始从右向左找'l'字符首次出现的位置
	cout << pos3 << endl; //9
}

③find_first_of()

#include<iostream>
using namespace std;
int main()
{
	//find_first_of用于查找指定字符串的所有字符在原字符串中最先出现的字符的位置
	string s1 = "hello world";
	string s2 = "aeiou";
	size_t pos = s1.find_first_of(s2); //查找s2中的所有字符中最先在s1中出现的字符的位置
	cout << pos << endl; //1
	 
	//find_first_of有何意义?
	//假如说想要把s3中的所有元音字母替换成'*'
	string s3 = "hello world";
	size_t found = s3.find_first_of("aeiou");
	while (found != string::npos) //查找完find_first_of会返回npos
	{
		s3[found] = '*';
		found = s3.find_first_of("aeiou", found + 1); //从下标为found+1的位置继续寻找
	}
	cout << s3 << endl; //h*ll* w*rld
}

④find_last_of()

 与 find_first_of 功能是一样的,不过 find_first_of 从左往右找,find_last_of 从右往左找

#include<iostream>
using namespace std;

int main()
{
    string s1 = "hello world";
    string s2 = "aeiou";
    
    // 查找元音字母最后一次出现的位置
    size_t pos = s1.find_last_of(s2);
    cout << pos << endl; // 输出:7(字母 'o' 的位置)
    
    return 0;
}

⑤find_first_not_of()

 从左向右找不在指定字符串中的字符

#include<iostream>
using namespace std;
int main()
{
	//find_first_not_of用于查找不在指定字符串的所有字符在原字符串中最先出现的字符的位置
	string s1 = "hello world";
	string s2 = "aeiou";
	size_t pos = s1.find_first_not_of(s2); //查找不在s2中的所有字符中最先在s1中出现的字符的位置
	cout << pos << endl; //0

	string s3 = "hello world";
	size_t found = s3.find_first_not_of("aeiou");
	while (found != string::npos) 
	{
		s3[found] = '*';
		found = s3.find_first_not_of("aeiou", found + 1); //从下标为found+1的位置继续寻找
	}
	cout << s3 << endl; //*e**o**o***
}

⑥find_last_not_of()

从右往左找不在指定字符串中的字符

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

int main() 
{
    string str = "Hello123World456";
    string targets = "0123456789";  // 数字字符集
    
    // 从后往前查找第一个不在目标字符集中的字符
    size_t pos = str.find_last_not_of(targets);
    if (pos != string::npos)
    {
        cout << "从后往前第一个非数字字符位置: " << pos << endl; //12
        cout << "该字符是: '" << str[pos] << "'" << endl; //d
    }
    return 0;
}

⑦substr()

 substr()用于获取子串,返回获取到的子串

#include<iostream>
using namespace std;
int main()
{
	string s1 = "hello world";
	string s2 = s1.substr(1, 3); //获取从下标为1开始的3个字符作为原字符串子串返回
	cout << s2 << endl; //ell

	string s3 = s1.substr(1); //s1从下标为0开始取完赋值给s3
	cout << s3 << endl; //ello world

	string s4 = s1.substr(); //取完整个s1
	cout << s4 << endl; //hello world
}

查找系列综合题目:获取网址的三部分

ps:网址有三部分构成:协议,域名,资源名;    ( : 和 / 将网址划分为了三部分)

#include<iostream>
using namespace std;
int main()
{
	string s1 = "https://legacy.cplusplus.com/reference/string/string/rfind/";
	string sub1, sub2, sub3;
	size_t i1 = s1.find(':');
	if (i1 != string::npos)
		sub1 = s1.substr(0, i1);
	size_t i2 = s1.find('/', i1 + 3);
	if (i2 != string::npos)
		sub2 = s1.substr(i1 + 3, i2 - (i1 + 3));
	sub3 = s1.substr(i2 + 1);
	cout << sub1 << endl; //https
	cout << sub2 << endl; //legacy.cplusplus.com
	cout << sub3 << endl; //reference/string/string/rfind/
}

获取底层C风格字符串起始地址

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1("hello");
	cout << s1.c_str() << endl;
}

关系运算符重载

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1("hello");
	string s2("world");
	cout << (s1 == s2) << endl; //0
	cout << (s1 < "haha") << endl; //0
}

字符串读入问题

C++中关于输入我们最常用的是cin, 但是cin有个缺陷,就是其默认的读取行为遵循 读取前跳过空白符 + 读取到下一个空白符停止,而空格和换行都是空白符,cin是无法直接读取到的!

如果想直接读取一整行输入,就需要用到 getline了

①不指定界定符,默认以回车结束,因此可以直接读取一行

#include<iostream>
using namespace std;
#include<string>
int main()
{
	//getline读取一整行
	string s1;
	getline(cin, s1);
	cout << s1 << endl;
}

② 指定第三个参数定界符,则 getline 会一直读取,直到遇到我们指定的定界符才会结束

#include<iostream>
using namespace std;
#include<string>
int main()
{
	string s1;
	getline(cin, s1, '@');
	cout << endl << s1 << endl;
	return 0;
}

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐