C++——string的了解和使用
目录
引言
为什么要学习string
1.C语言中的字符串
2.C++中的字符串
auto和范围for
1.auto
1.1 auto的介绍
1.2 注意事项
2.范围for
标准库中的string类
1.string类的迭代器
1.1 begin()与end()函数
1.2 rbegin()与rend()函数
2.string类的初始化和销毁
3.string类的容量操作
3.1 有效长度和容量大小
(1)使用示例
(2)扩容机制
3.2 有效长度和扩容操作
(1)reserve函数
(2)resize函数
(3)clear函数
4.string类的访问操作
4.1 operator[]函数
4.2 at函数
4.3 front 和 back 函数
5.string类的修改操作
5.1 字符串增加操作
5.2 字符串替换操作
5.3 字符串删除操作
5.4 字符串交换操作
5.5 字符串其他操作
结束语
引言
为了简化字符串的处理,C++标准库引入了string类。string类是一个模板类,专门用于表示和操作字符串。今天我们来学习一下string。
为什么要学习string
1.C语言中的字符串
C语言中,字符串是以 '\0' 结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
2.C++中的字符串
为了简化字符串的处理,C++标准库引入了string类。string类是一个模板类,专门用于表示和操作字符串。它不仅封装了字符串的底层细节,如内存分配和释放,还提供了一套丰富而易于使用的成员函数,用于执行各种字符串操作,如拼接、比较、查找、替换、插入和删除等。
下面是一个string的简单使用:
int main()
{
string s1;
string s2("hello world");
string s3(s2);
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
string s4(s2, 6, 15);
cout << s4 << endl;
string s5(s2, 6);
cout << s5 << endl;
string s6("hello world", 5);
cout << s6 << endl;
string s7(10, 'x');
cout << s7 << endl;
return 0;
}
输出结果为:
下面我们来详细学习一下。
auto和范围for
在学习string类之前,我们先来学习一下auto和范围for,方便我们后面的学习。
1.auto
1.1 auto的介绍
auto 关键字用于自动类型推导。编译器会自动根据初始化表达式来推断变量的类型。这避免了在声明变量时显式指定类型,特别是当类型名称很长或复杂时,auto可以使代码更加简洁。
int main()
{
int i = 0, & ri = i;
auto a = i; //a为int型变量
auto a1 = ri; //a1为int型变量
auto p = &i; // &i 是一个普通int指针,p是一个整型指针int *
auto p1 = &ri; //同上
return 0;
}
1.2 注意事项
(1)使用 auto 声明的变量必须有初始化表达式,否则编译器无法推导其类型。
因此,不能这样子:
auto x;
(2)对于模板实例化、复杂表达式等,auto 能够推导出相应的复杂类型
std::vector<int> vec = {1, 2, 3};
auto v = vec; // v 的类型是 std::vector<int>
auto result = Fun(x, y); // result 的类型由函数返回类型决定
(3)auto也有一些缺陷,例如它降低了代码的可读性,导致我们无法一眼看出变量的类型(像上面的代码,我们不能直观的看出 v 的类型)。
(4)用 auto 声明指针类型时,用 auto 和 auto* 没有任何区别,只是 auto* 必须是指针。但是用 auto 声明引用类型时,必须加 & 。
int x = 1;
auto a = &x;
auto* b = &x;
auto& c = x;
2.范围for
范围for(也称为基于范围的for循环)是C++11引入的一种新的循环语法,用于简化对容器(如vector、list、map等)、数组或初始化列表中的元素进行遍历的操作。这种循环语法使得代码更加简洁、清晰,提高了代码的可读性和可维护性。
范围for循环的基本语法如下:
for (元素类型 变量名 : 容器或数组名)
{
// 循环体
}
来看个简单的例子:
int main()
{
int arr[] = { 1,2,3,4,5 };
for (auto i : arr)
{
cout << i << endl;
}
return 0;
}
输出结果为:
由于 i 是临时变量,不能直接修改数组元素的值,我们需要使用引用 & 。
int main()
{
int arr[] = { 1, 2, 3, 4, 5 };
for (auto& i : arr) // 注意这里使用了引用&
{
i *= 2; // 修改数组元素的值
cout << i << endl;
}
return 0;
}
输出结果为:
标准库中的string类
我们可以使用以下文档来辅助我们学习string:
string类的接口详细介绍
1.string类的迭代器
迭代器(Iterator)在C++中是一种抽象化的概念,它提供了一种统一的方法来遍历容器中的元素,而无需了解容器内部的具体实现细节。
迭代器在行为上类似于指针,但它不仅仅局限于原始指针的功能。迭代器封装了对容器元素的访问,并且根据容器的不同,迭代器可以有不同的实现。然而,从用户的角度来看,迭代器提供了一种统一的接口来遍历容器,这使得代码更加通用和可移植。
当我们使用迭代器时,确实可以将其当作指针来使用,但需要注意的是,迭代器并不是指针的替代品,而是对容器遍历操作的一种抽象。
在 string 类中,我们就可以通过迭代器来访问其具体元素,并且也为我们提供了相应的调用函数。
1.1 begin()与end()函数
迭代器提供了 begin() 和 end() 成员函数,用于获取遍历的起始位置和结束位置。
begin()与end()函数的使用方法具体如下:
begin():
函数声明:
对于非const的std::string对象,iterator begin(); 返回指向第一个字符的iterator。
对于const的std::string对象,const_iterator begin() const; 返回指向第一个字符的const_iterator。
作用:返回指向字符串第一个字符的迭代器。
返回值:
对于非const对象,返回iterator,允许通过迭代器修改字符串中的字符。
对于const对象,返回const_iterator,仅允许通过迭代器读取字符串中的字符,不允许修改。
end() :
函数声明(对于std::string):
iterator end(); 返回指向字符串末尾(即最后一个字符之后的位置)的iterator。
const_iterator end() const; 对于const对象,返回指向字符串末尾的const_iterator。
作用:返回指向字符串末尾(即最后一个字符之后的位置)的迭代器。这个迭代器通常用作遍历循环的结束条件。
我们来尝试使用一下这俩玩意,代码如下:
int main()
{
string s1("hello world");
cout << s1 << endl;
string::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it << " ";
++it;
}
cout << endl;
return 0;
}
输出结果为:
int main()
{
const string s2("hello world");
cout << s2 << endl;
// const迭代器,用于遍历const字符串,不能改变字符串中的值
string::const_iterator it = s2.begin();
while (it != s2.end())
{
cout << *it << " ";
++it;
}
cout << endl;
return 0;
}
输出结果为:
1.2 rbegin()与rend()函数
rbegin:
函数声明(对于 std::string 和其他支持反向迭代器的容器):
reverse_iterator rbegin();
const_reverse_iterator rbegin() const;
作用:
返回指向容器(如 std::string)中最后一个元素(即反向遍历的起始位置)的反向迭代器。这使得可以反向遍历容器中的元素。
返回值:
对于非 const 容器,返回 reverse_iterator。对于 const 容器,返回const_reverse_iterator。
rend:
函数声明(对于 std::string 和其他支持反向迭代器的容器):
reverse_iterator rend();
const_reverse_iterator rend() const;
作用:
返回指向容器(如 std::string)中第一个元素前面一个位置的反向迭代器。这实际上是反向遍历的结束位置,它不指向任何有效元素,而是作为反向遍历的结束标志。
返回值:
对于非 const 容器,返回 reverse_iterator。 对于 const 容器,返回 const_reverse_iterator。
来简单的使用一下:
int main()
{
string s1("hello world");
cout << s1 << endl;
string::reverse_iterator rit = s1.rbegin();
while (rit != s1.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
return 0;
}
输出结果为:
int main()
{
const string s2("hello world");
cout << s2 << endl;
string::const_reverse_iterator rit = s2.rbegin();
while (rit != s2.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
return 0;
}
输出结果为:
2.string类的初始化和销毁
由于 string 是一个类,因此我们在初始化时肯定会调用其构造函数初始化。
(1)默认构造函数:创建一个空的字符串对象。
(2)拷贝构造函数:创建一个新的字符串对象,它是另一个字符串对象的副本。这允许通过现有的字符串来初始化新字符串。
(3)子字符串构造函数:创建一个新的字符串对象,它是另一个字符串对象从pos位置开始、长度为len的子串。如果len是npos(默认情况),则子串从pos开始直到原字符串的末尾。
(4)从C字符串构造函数:使用C风格的字符串(以空字符'\0'结尾的字符数组)来初始化字符串对象。
(5)从C字符串的前n个字符构造函数:使用C风格的字符串的前n个字符来初始化字符串对象。
(6)填充构造函数:创建一个新的字符串对象,其中包含n个重复的字符c。
(7)范围构造函数:这是一个模板构造函数,允许使用任何输入迭代器对(如指针、std::vector的迭代器等)来初始化字符串。它复制从first到last(不包括last)范围内的所有元素到新的字符串对象中。
(1)字符串赋值:这个重载版本允许将一个 std::string 对象的内容赋值给另一个 std::string 对象。这是通过复制 str 的内容到当前对象来实现的。注意,这里使用了引用传递(const string&),这有助于避免不必要的复制,提高效率。
(2)C风格字符串赋值:这个重载版本允许将一个C风格字符串(即以空字符('\0')结尾的字符数组)赋值给 std::string 对象。std::string 会复制 s 指向的C风格字符串的内容(不包括结尾的空字符),并存储在自己的内部表示中。
(3)单个字符赋值:这个重载版本允许你将一个单独的字符赋值给 std::string 对象。这通常会导致 std::string 对象的内容被替换为仅包含该字符的一个新字符串。
下面是代码演示:
int main()
{
// (1)默认构造函数。
string s1;
s1 = "hello world!";
//(2)拷贝构造函数
string s2(s1);
//(3)子字符串构造函数
string s3(s2, 1, 7);
//(4)从C字符串构造函数
string s4("hello world!");
//(5)从C字符串的前n个字符构造函数
string s5("hello world!", 5);
//(6)填充构造函数
string s6(7, 'a');
//(7)范围构造函数
string s7(s1.begin(), s1.end());
// 赋值运算符重载初始化
string s8 = "hello world!";
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
cout << s5 << endl;
cout << s6 << endl;
cout << s7 << endl;
cout << s8 << endl;
return 0;
}
输出结果为:
在C++中,std::string 是一个类模板的实例化,代表了一个可变的字符序列。由于它是一个类,当 std::string 对象的生命周期结束时(即离开其作用域),编译器会自动调用该对象的析构函数来清理其分配的资源。
3.string类的容量操作
在C++中,std::string 类提供了多种与容量(capacity)相关的操作,这些操作允许你查看或修改字符串的内部存储空间的大小。
以下是比较常见的容量操作:
函数名称 | 功能 |
size | 返回字符串的有效长度 |
length | 返回字符串的有效长度 |
capacity | 返回字符串的容量大小 |
max size | 返回字符串的最大长度 |
clear | 清空字符串 |
empty | 检查是否为空串,是则返回true,否则返回false |
reserve | 请求改变字符串的容量 |
resize | 重新设置有效字符的数量,超过原来有效长度则用c字符填充 |
3.1 有效长度和容量大小
(1)使用示例
std::string 的容量(capacity)并不总是等于其当前长度(size)。容量是字符串为了存储更多字符而预先分配的内存量,而长度是当前存储在字符串中的字符数。
我们通过代码来验证一下:
int main()
{
string s("hello world");
cout << s.size() << endl; //有效长度
cout << s.length() << endl; //有效长度
cout << s.capacity() << endl; //容量大小
cout << s.max_size() << endl; //最大大小
return 0;
}
输出结果为:
其中有效长度 size 以及容量大小 capacity 不包括\0。而 max_size 返回字符串最大容量,不同平台下大小可能不一样。
还有一个判空函数empty(),下面是一个简单的使用示例:
int main()
{
string s1("");
string s2("hello world");
if (s1.empty())
{
cout << "s1为空串" << endl;
}
else
{
cout << "s1不为空串" << endl;
}
if (s2.empty())
{
cout << "s2为空串" << endl;
}
else
{
cout << "s2不为空串" << endl;
}
return 0;
}
输出结果为:
(2)扩容机制
我们接下来研究一下string的扩容机制:
来看看这段代码:
int main()
{
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';
}
}
return 0;
}
输出结果为:
我们可以看到,在vs2022中,string大致以一点五倍进行扩容处理。我们来看看在g++编译器的运行结果:
我们可以看到,在这里是二倍扩容处理。
3.2 有效长度和扩容操作
我们先来学习两个可以改变容量的函数 reserve,resize。
(1)reserve函数
reserve函数用于请求改变字符串的容量(capacity),即为其内部存储空间分配至少能容纳n个字符的空间。
来测试一下:
int main()
{
string s1;
s1.reserve(50);
cout << s1.capacity() << endl;
return 0;
}
输出结果为:
系统开辟的空间往往是大于程序员设定的空间的,且遵循对齐原则。
(2)resize函数
resize函数用于改变字符串的大小(size),即改变其中包含的字符数。
如果 n 小于当前字符串的长度,则 resize 函数会将字符串缩短到前 n 个字符,并删除超出第 n 个字符之后的所有字符。
如果 n 大于当前字符串的长度,则 resize 函数会扩展字符串,以便它能够包含 n 个字符。这通常涉及到在字符串的末尾添加新的字符。
如果调用时提供了第二个参数 c,则这些新添加的字符将被初始化为 c 的副本。
如果没有提供第二个参数 c,则新添加的字符是值初始化的。
n<size:
测试代码:
void test1()
{
string s1 = "hello world";
cout << "字符个数:" << s1.size() << endl;
cout << "空间大小:" << s1.capacity() << endl << endl;
s1.resize(10);
cout << "修改后字符个数:" << s1.size() << endl;
cout << "修改后空间大小:" << s1.capacity() << endl;
}
输出结果:
size < n < capacity:
测试代码:
void test2()
{
string s1 = "hello world";
cout << "字符个数:" << s1.size() << endl;
cout << "空间大小:" << s1.capacity() << endl << endl;
s1.resize(12);
cout << "修改后字符个数:" << s1.size() << endl;
cout << "修改后空间大小:" << s1.capacity() << endl;
}
输出结果为:
n>capacity:
测试代码:
void test3()
{
string s1 = "hello world";
cout << "字符个数:" << s1.size() << endl;
cout << "空间大小:" << s1.capacity() << endl << endl;
s1.resize(20);
cout << "修改后字符个数:" << s1.size() << endl;
cout << "修改后空间大小:" << s1.capacity() << endl;
}
输出结果:
(3)clear函数
string 类提供的 clear 函数用于移除字符串中的所有字符,使其变为空字符串。
下面是个简单的测试:
int main()
{
string str = "Hello, World!";
// 检查字符串是否为空
if (str.empty())
{
cout << "串为空" << endl;
}
else
{
cout << "串不为空" << endl;
}
// 清除字符串
str.clear();
// 检查字符串是否为空
if (str.empty())
{
cout << "串为空" << endl;
}
else
{
cout << "串不为空" << endl;
}
// 输出清空后的字符串
cout << "清空后的字符串: " << str << endl;
return 0;
}
输出结果为:
4.string类的访问操作
string类的访问操作通常有如下几种:
函数名称 | 功能 |
operator[] | 返回指定位置的字符,越界则报错 |
at | 返回指定位置的字符,越界则抛异常 |
back | 返回字符串最后一个字符(不是"0”) |
front | 返回字符串第一个字符 |
4.1 operator[]函数
string类的operator[]函数是一个重载的下标操作符,它允许通过索引来访问字符串中的单个字符。
string 类重载了两个operator[],一个是普通版的,一个是 const ,即字符串不可修改的。
简单的测试:
int main()
{
string s1 = "hello world";
cout << s1[0] << endl;
cout << s1[2] << endl;
cout << s1[4] << endl;
return 0;
}
输出结果为:
普通版的话我们可以对字符串进行修改。
int main()
{
string s1 = "hello world";
s1[0] = 'x';
s1[1] = 'x';
s1[2] = 'x';
cout << s1 << endl;
return 0;
}
输出结果为:
4.2 at函数
at函数提供了一种安全的方式来访问字符串中的字符。与operator[]不同,at函数会执行范围检查,以确保提供的索引没有超出字符串的当前长度。
int main()
{
string s1("hello world");
cout << s1.at(0) << endl;
cout << s1.at(2) << endl;
return 0;
}
输出结果为:
4.3 front 和 back 函数
这两个用的比较少,了解即可。
int main()
{
string s1("hello world");
cout << s1.front() << endl;
cout << s1.back() << endl;
return 0;
}
输出结果如下:
5.string类的修改操作
关于string类的操作修改的接口比较多,由于篇幅有限,实在不能详细一个个讲解,我们就先学习一些比较重要的,其他的有需要再去了解。
函数名称 | 功能说明 |
push_back | 在字符串后尾插字符c |
append | 在字符串后追加一个字符串 |
operator+= | 在字符串后追加字符串str |
assign | 使用指定字符串替换原字符串 |
replace | 用新字符串替换原字符串指定区间 |
pop_back | 删除字符串最后一个字符 |
erase | 删除字符串指定部分 |
swap | 交换两个字符串 |
5.1 字符串增加操作
字符串的增加操作,在末尾添加字符可以使用push_back,在末尾添加字符串可以使用append,而operator+=既可以在末尾添加字符,也可以添加字符串。
int main()
{
string str("hello world");
// 追加一个!
str.push_back('!');
cout << str << endl;
str += '!';
cout << str << endl;
// 在字符串开头插入一个'!'
str.insert(0, 1, '!');
cout << str << endl;
// 追加一个字符串
str.append("hello ");
cout << str << endl;
str += "world!";
cout << str << endl;
return 0;
}
输出结果为:
5.2 字符串替换操作
字符串替换操作我们用到的是assign和replace。
assign是直接替换掉原字符串,而replace是替换原字符串的某段区间。
示例如下:
int main()
{
std::string s1("hello world");
std::string s2;
s2.assign(s1);
std::cout << s2 << std::endl;
s2.assign(s1, 6);
std::cout << s2 << std::endl;
// 用s1替换掉下标为2,长度为2的区间
std::string s3("ciallo");
s3.replace(2, 2, s1);
std::cout << s3 << endl;
}
输出结果:
这俩函数接口比较多,可以自行查看文档进行学习。
5.3 字符串删除操作
pop_back支持删除最后一个字符,erase支持删除任意区间。
int main()
{
string str("hello world");
str.pop_back();
cout << str << endl;
str.erase(str.begin());
cout << str << endl;
str.erase(0, 2);
cout << str << endl;
return 0;
}
输出结果为:
5.4 字符串交换操作
swap 函数用于交换两个字符串对象的内容。
int main()
{
string s1("hello world");
string s2("C++");
cout << s1 << endl;
cout << s2 << endl;
s1.swap(s2);
cout << s1 << endl;
cout << s2 << endl;
return 0;
}
输出结果为:
5.5 字符串其他操作
除了以上这些,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<< | 流提取重载 |
operator>> | 流插入重载 |
下面就不展开说了,可以通过查看文档来进行学习。
结束语
本篇博客写的有点长了。。。
希望能对各位大佬有所帮助!!!
求点赞收藏评论关注!!!
感谢各位!!!