目录
一.预备知识
1.auto关键字
2.范围for
3.迭代器
二.标准库里的string
1.string类的基本介绍
2.构造函数
编辑
3.访问及遍历操作
3.1 operator []
3.2 基于范围for
3.3 使用迭代器
4.迭代器
5.容量操作
5.1 size和length
5.2 capacity
5.3 reserve和resize
6.修改操作
6.1 operator+= 和 append
6.2 insert
6.3 erase
编辑
7.查找操作
7.1 find
7.2 rfind
7.3 substr
一.预备知识
1.auto关键字
在C++11中,标准委员会赋予了auto全新的含义:作为一个类型指示符来指示编译器在编译时期推导变量的类型。这使得代码更简洁,尤其是在处理复杂的类型时。
用法示例:
1.声明普通变量
auto x = 1; // x 的类型被推断为 int auto y = 1.11; // y 的类型被推断为 double
2.声明指针变量
int i = 10; auto p = &i; // p 的类型被推断为 int* auto* q = &i; // q 的类型同样被推断为 int*
3.声明引用类型
int j = 20; auto& r = j; // r 的类型被推断为 int&
4.在同一行声明多个变量
auto x1 = 1, x2 = 2; // 正确,x1 和 x2 的类型都推断为 int auto y1 = 1, y2 = 3.14; // 错误,y1 和 y2 的类型不同
5.auto不能直接声明未初始化变量(需要初始值设定)
6.auto不能作为函数的参数
// 错误用法 void func(auto n) {// ... }
7.auto不能直接声明数组
// 错误用法 auto arr[5]; // 错误,无法推断数组类型
auto常在范围for中使用,接下来就介绍范围for
2.范围for
对于一个有范围的集合而言,由程序员还说明循环的范围是多余的,有时候还容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号:分为两部分,冒号前是用于迭代的变量,冒号后是被迭代的范围,自动迭代,自动取数据,自动判断结束。
范围for可以作用到数组和容器对象(array、string、vector、list...)上进行遍历
int main()
{int array[] = { 1, 2, 3, 4, 5 };//遍历数组每一个值,都乘以2,结果为 2,4,6,8,10//自动识别类型,用引用能改变原值for (auto& e : array)e *= 2;//范围for遍历数组array并打印for (auto e : array)cout << e << ' ';cout << endl;//范围for遍历字符串str并打印string str("hello world");for (auto ch : str){cout << ch << ' ';}cout << endl;return 0;
}
范围for的底层很简单,容器的遍历实际上就是替换为迭代器,接下来就介绍迭代器
3.迭代器
迭代器是一种数据类型,在C++中,迭代器实际上是一种对象,它被设计用于在容器中进行元素的遍历和访问。迭代器为程序员提供了一种抽象的方式来访问容器的内容,而不用关心容器的底层实现细节。
例如以下用法,目前还没正式讲到,可以看作一个指针来进行理解:其中string::iterator就是string类的迭代器类型,it是迭代器的变量名,将其看作指针,begin就是开头处,end就是结尾处,*it就是解引用该指针指向的内容,it++使得指针指向下一个内容,如此,大概就是迭代器的遍历操作
int main()
{string s1 = "abcde";string::iterator it = s1.begin();while (it != s1.end()){cout << *it << " ";it++;}return 0;
}
在string类的标准库和模拟实现中,会具体介绍并进行模拟实现,加深印象。
二.标准库里的string
1.string类的基本介绍
在C语言中,字符串是以'\0'结尾的字符数组,需要手动管理内存和处理字符串操作。在头文件string.h中提供了一系列库函数,如strlen、strcpy、strcat等来对字符串进行操作。但这些函数和字符串是分离的,需要手动管理内存,容易出现越界访问等问题。
因此,在C++标准库中,提供了string类,它封装了字符串操作,提供了丰富的成员函数和运算符重载,使得字符串的操作更加方便和安全。
实际上string类是C++标准库的一部分,学习标准库,是一定需要去读文档的,本文依照该string类的文档介绍进行:string - C++ Reference (plusplus.com)
string在底层实际是basic_string这个模板类使用char类型实例化得到的一个具体类,当然我们在使用时直接使用string即可,它本身就是basic_string<char>
2.构造函数
string作为一个类,构造函数是必不可少的,常用的如下几个:
构造函数名称 | 功能说明 |
---|---|
string() | 默认构造函数,构造空的string类对象,即空字符串 |
string(const string& s) | 拷贝构造函数 |
string(const char* s) | 用C-string(C风格的字符串)来构造 |
string(size_t n, char c) | string类对象中包含n个字符c |
#include<iostream>
using namespace std;
int main()
{string s1;string s2("abcdefg");string s3(10, 'a');string s4(s2);cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;cout << s4 << endl;return 0;
}
3.访问及遍历操作
3.1 operator []
重载下标操作符[ ],使得可以像访问数组一样使用下标来访问字符串中的某个字符,同时返回值是char&,这意味着我们还可以对该下标的字符进行修改等操作。
int main()
{string s1 = "abcde";//进行遍历,遍历方式类似于数组for (int i = 0; i < s1.size(); ++i){cout << s1[i] << " ";}cout << endl;//对下标为0的字符(即第一个字符)进行更改s1[0] = '0';cout << s1;return 0;
}
3.2 基于范围for
int main()
{string s1 = "abcde";//只能遍历for (auto e: s1){cout << e << " ";}cout << endl;//若要进行更改,加&引用for (auto& e : s1){e = 'a';cout << e << " ";}cout << endl;return 0;
}
3.3 使用迭代器
在遍历的实现中,更推荐使用迭代器,因为迭代器是大多数数据结构都拥有的,而[ ]不是都有的,使用[ ] 要求物理底层是连续的,如数组等。
int main()
{string s1 = "abcde";string::iterator it = s1.begin();while (it != s1.end()){cout << *it << " ";it++;}return 0;
}
4.迭代器
迭代器是一种数据结构,在C++中,迭代器是一种对象,它被设计用于在容器中进行元素的遍历和访问。迭代器为程序员提供了一种抽象的方式来访问容器中的元素,而不用关心容器的底层实现细节。
在string类中就有这四个比较重要的迭代器:
迭代器名称 | 功能说明 |
---|---|
begin() | 返回一个指向字符串中第一个字符的迭代器 |
end() | 返回一个指向字符串中最后一个字符的下一个位置的迭代器 |
rbegin() | 反向,返回一个指向字符串中最后一个字符的迭代器 |
rend() | 反向,返回一个指向字符串中第一个字符的前一个位置的迭代器 |
string类提供了很多类型的迭代器,包括正向迭代器(iterator)、常量正向迭代器(const_iterator)、反向迭代器(reverse_iterator)、常量反向迭代器(const_reverse_iterator)
- 正向迭代器(iterator):用于遍历可修改字符串的迭代器,可以通过begin()、end()方法来获取范围
- 常量正向迭代器(const_iterator):用于遍历不可修改的常量对象的迭代器,也可以通过begin()、end()方法获取范围
- 反向迭代器(reverse_iterator):用于反向遍历可修改的字符串的迭代器,可以通过rbegin()、rend()方法来获取范围
- 反向常量正向迭代器(const_reverse_iterator):用于反向遍历不可修改的常量对象的迭代器,也可以通过rbegin()、rend()方法获取范围
根据begin()的定义,可以发现这其实是一个重载,一个适用于非常量对象,一个适用于常量对象,返回的迭代器也因此不同,例如可以这样使用:
int main()
{string s = "abcde";const string cs = "abc";//会取寻找最合适的,此时非常量对象两者都可以,不过使用普通迭代器最合适string::iterator it = s.begin();//但是权限不能放大,只能缩小//不能使用普通迭代器去接收常量字符串的迭代器//因此用string::iterator cit = cs.begin()会报错//应该使用常量迭代器string::const_iterator cit = cs.begin();return 0;
}
其他的end();rbegin();rend()都是同理,此处不再赘述。
5.容量操作
函数名称 | 功能说明 |
---|---|
size() | 返回字符串有效字符长度(容器通用) |
length() | 返回字符串有效字符长度(字符串专属) |
capacity() | 返回当前分配给字符串的总空间大小 |
empty() | 检测字符串是否为空字符串 |
clear() | 清空字符串有效内容 |
reserve() | 为字符串预留空间 |
resize() | 将有效字符的个数改为n个,多出的空间用字符c填充 |
5.1 size和length
两者的作用完全相同:都是返回字符串的有效长度,但是length是只适用于字符串string类的,而size是通用于容器的。
5.2 capacity
capacity()用于返回当前字符串对象分配的储存空间大小(即容量),字符串是存在容量的,这个容量就是指在不重新分配内存的情况下,字符串可以存储的最大字符数。
值得注意的是,capacity()返回的是字符串分配的总空间大小,而不是当前字符串的实际长度,也就是说,capacity()的返回值会大于size()的返回值,因为容量是一定大于等于实际储存的字符长度的,例如:
5.3 reserve和resize
reserve有保留,预定的意思,功能就是为字符串预留至少n个字符的储存空间,也就是提前分配指定大小空间,好处是节约扩容成本。改变的是容量的大小,而不是实际有效字符的大小。
- 当n大于当前容量时,reserve就会重新分配内存(可能扩容到n但也可能按其他方式进行扩容到比n大的数目)
- 若n小于容量,有可能进行缩容也有可能不做处理,这取决于编译器和环境
但有一点是无可置疑的:reserve不会影响到字符串的实际大小与内容,size()的值一定不会变化
resize功能是调整字符串的实际大小为n。根据需要插入或删除字符,使得字符串的实际大小等于n
- 若n小于当前字符串的实际大小,那么多余的字符将被删除,只剩下前n个字符
- 若n大于当前字符串的实际大小,那么将用指定的字符c(不指定默认是'\0')来填充新增的部分
6.修改操作
函数名称 | 功能说明 |
---|---|
operator += | 在字符串后追加字符串 |
push_back | 在字符串后插入字符 |
append | 在字符串后追加字符串 |
insert | 插入字符(字符串) |
erase | 删除字符 |
6.1 operator+= 和 append
将+=进行重载,用于将当前字符串与另外一个字符串进行连接,方便快捷,当然append也能做到该功能。
6.2 insert
常用的两个, 1号是在pos这个下标位置前插入str字符串,2号是在pos下标前插入n个字符c
由于插入需要挪动数据,因此insert的效率其实并不高
6.3 erase
删除从下标pos出开始算起的len的字符,注意这里len的默认值是npos,需要补充说明一下npos
npos是C++中的一个静态成员变量,表示无效或不存在的位置。通常用于标识字符串查找等操作未找到匹配项的情况。npos的类型是size_t,值为-1,其补码就是全1,也就是最大的意思。
7.查找操作
函数名称 | 功能说明 |
---|---|
find | 在字符串中查找指定的字符或子串 |
rfind | 在字符串中从右往左查找指定的字符或子串 |
find_first_of | 在字符串中从左往右查找指定集合中的任意字符 |
find_last_of | 在字符串中从右往左查找指定集合中的任意字符 |
find_first_not_of | 在字符串中从左往右查找不在指定集合中的最后一个字符 |
find_last_not_of | 在字符串中从右往左查找不在指定集合中的最后一个字符 |
substr | 从字符串中提取子串 |
7.1 find
find用于返回 一个字符或一个字符数组或一个string对象 在string中首次出现的位置,并返回下标,如果找不到就返回npos
7.2 rfind
整体和find类似。不过是从后往前找,找到一个字符或一个字符数组或一个string对象最后一次出现的位置,如果找不到就返回 npos
7.3 substr
从pos位置处开始截取len长度(默认截取到最后),下面以得到文件后缀为例:
int main()
{string s1("test.c");//得到文件后缀//从前往后找到第一个.的位置size_t pos1 = s1.find('.');//如果找到了就截取if (pos1 != string::npos){string ret1 = s1.substr(pos1);cout << ret1 << endl;}//如果文件名含有.怎么办string s2("te.st.c");//使用rfind从后往前查找倒数第一个.size_t pos2 = s2.rfind('.');if (pos2 != string::npos){string ret2 = s2.substr(pos2);cout << ret2 << endl;}return 0;
}
由于string类函数是很丰富的,这里不可能全部一一讲解,更多的需要在使用时去查阅相应的文档即可:<string> - C++ Reference (cplusplus.com)
在下一篇文章中将会具体自己模拟实现string类来加深理解,欢迎持续关注,感谢观看