一、string类概览:
1、string类的由来:
之所以抛弃char*的字符串而选用C++标准程序库中的string类,是因为他和前者比较起来,不必担心内存是否足够、字符串长度等等,而且作为一个泛型类出现,他集成的操作函数足以完成我们大多数情况下(甚至是100%)的需要。我们可以用 = 进行赋值操作,== 进行比较,+ 做串联(是不是很简单?)。我们尽可以把它看成是C++的基本数据类型。
C++中对于string的定义为:typedef basic_string<char> string
; 也就是说C++中的string类是一个泛型类,由模板而实例化的一个标准类,本质上不是一个标准数据类型。
在我们的程序中使用string类型,我们必须包含头文件 。如下:#include<string>
using namespace std; 此语句必不可少,否则有的编译器无法识别
1.2、string函数列表:
begin | 得到指向字符串开头的Iterator |
end | 得到指向字符串结尾的Iterator |
rbegin | 得到指向反向字符串开头的Iterator |
rend | 得到指向反向字符串结尾的Iterator |
size | 得到字符串的大小 |
length | 和size函数功能相同 |
max_size | 字符串可能的最大大小 |
capacity | 在不重新分配内存的情况下,字符串可能的大小 |
empty | 判断是否为空 |
operator[] | 取第几个元素,相当于数组 |
c_str | 取得c格式的const char* 字符串 |
data | 取得字符串内容地址 |
operator= | 赋值操作符 |
reserve | 预留空间 |
swap | 交换函数 |
insert | 插入字符 |
append | 追加字符 |
operator+= | += 操作符 |
erase | 删除字符串 |
clear | 清空字符容器中所有内容 |
resize | 重新分配空间 并初始化 |
assign | 和赋值操作符一样 |
replace | 替代 |
copy | 复制字符串到空间 |
find | 查找+ |
rfind | 反向查找 |
find_first_of | 查找包含子串中的任何字符,返回第一个位置 |
find_first_not_of | 查找不包含子串中的任何字符,返回第一个位置 |
find_last_of | 查找包含子串中的任何字符,返回最后一个位置 |
find_last_not_of | 查找不包含子串中的任何字符,返回最后一个位置 |
substr | 剪切字符串 |
compare | 比较字符串 |
operator+ | 字符串链接 |
operator== | 判断是否相等 |
operator!= | 判断是否不等于 |
operator<<//td> | 判断是否小于 |
operator>> | 从输入流中读入字符串 |
operator<< | 字符串写入输出流 |
getline | 从输入流中读入一行 |
对于这些函数,我们在下面将会逐个讲解:
2、string常用接口:
对于这些用法来说,下面传入的参数个数在同一个用法中都有所差异,这是因为每一个成员函数都支持了重载。
在介绍下述接口之前,需要知道的是,由于每一个函数都有重载,因此有的有const的重载,有的没有,其实这是以函数的需求从而判断其有无const类型的重载函数,这里提前进行总结:
1.只读功能函数 const版本
2.只写功能函数 非const版本
3.读写功能的函数 const+非const版本
2.1、初始化:
初始化有两种方式,其中使用等号的是拷贝初始化,不使用等号的是直接初始化。(注释后面是打印的结果)
但对于使用等号的和str(str1),即一个变量通过另一变量初始化的,都是拷贝构造。(深拷贝)
//初始化
string str1 = "hello world"; //str1 = "hello world"
cout << str1 << endl;
string str2("hello world"); //str2 = "hello world"
cout << str2 << endl;
string str3 = str2; //本质上是拷贝构造
string str4(str3);
cout << str3 << endl << str4 << endl;
string str5(10, 'x'); //str5 = "xxxxxxxxx"
cout << str5 << endl;
string str6 = string(10, 'x'); //str6 = "xxxxxxxxx"
cout << str6 << endl;
string str7(str1, 6); //str7 = "world" 从字符串str1的第6个字符开始到结束,拷贝到str7
cout << str7 << endl;
string str8(str1, 6, 3); //str8 = "wor" 从字符串str1的第6个字符开始的三个字符,拷贝到str8
cout << str8 << endl;
string str9(str1, 0, 5); //str9 = "hello" 从字符串str1的第0个字符开始的五个字符,拷贝到str9
cout << str9 << endl;
char c[] = "hello world";
string str10(c, 5); //str10 = "hello" 将字符串c中前5个字符拷贝到str10
cout << str10 << endl;
2.2、string::npos :
我们观察一下上面str7与str_7的区别,想必大家已经看出,这里的str7和str_7是同一个重载函数,并且这个函数具有缺省值,当我们不传入最后一个参数时,其就会一直拷贝到字符串的末尾为止。那这个缺省参数是什么呢?我们查阅文档得知,是npos:
我们发现,npos的值规定为-1,但实际上因为是size_t类型,所以这是一个无符号的数字,即此-1并不是十进制的-1,而是:4294967295
因此,此位置重载在不输入数值时默认为此值,也就能够遍历到字符串的末尾了。
2.3、c_str :
对于string类来说,其内部有这么一个成员变量,c_str,正如此图,c_str本身和指向的值均不能改变,返回值是
char*
实际上返回的就是string类中的内容的地址,也就是字符串的地址。
那c_str有什么作用呢?事实上对于一些线程,网络,Linux内核等都是通过C实现的,因此c_str很好的充当了一个C++中string与C之间的互通,因为我们知道,对于string定义的变量名,不是内部字符串的地址,因此就出现了c_str()返回内容的地址,从而解决这个问题。
比如:
2.4、获取长度(length、size):
length()函数与size()函数均可获取字符串长度。但除了string,其他类型就只有size()。
string str = "hhhhhhhhhh";
cout << str.length() << endl;
cout << str.size() << endl;
当str.length()与其他类型比较时,建议先强制转换为该类型,否则会意想之外的错误。
比如:-1 > str.length() 返回 true。
2.5、容量(capacity、reserve、resize):
对于size和capacity来说,大家在学了顺序表之后并不陌生,size是实际长度,而capacity代表着容量的大小,对于string类来说,其也具有这样的成员变量(对应值C语言顺序表中结构体内部的的size、capacity),而这里的扩容规则在每一个平台也是不一样的,比如我的linux和vs2019二者之间就有很大的区别,不过我们并不需要关心他,因为string作为内部类,其扩容的机制已经被写在该类之中。
下面我们看一下string扩容规律:
string str1;
size_t sz = str1.capacity();
while (sz < 100)
{
str1 += 'x';
if (sz != str1.capacity())
{
sz = str1.capacity();
}
}
扩容前str1的size和capacity:
扩容后:
接着扩容:
可以看出第一次扩容那个,大概是按二倍进行的,而之后按大概一点五倍扩容。
此外,还有其他函数也属于容量的范畴:
resize可以改变成员的size()的大小。
reserve可以改变成员的capacity()的大小。
//str.reserve()
//str.resize()
string str2;
str2.reserve(100);
cout << str2.size() << endl;
cout << str2.capacity() << endl;
string str3;
str3.resize(100, 'x');
cout << str3.size() << endl;
cout << str3.capacity() << endl;
reserve是开辟并预留空间,resize是开辟并将空间进行初始化。
2.6、插入(insert):
string str("献出心脏");
string str1("士兵们,");
string str2("~塔塔开");
//str.insert(pos,n,ch) 在字符串str的pos位置处插入n个字符ch
str.insert(8, 3,'!'); //str = "献出心脏!!!"
cout << str << endl;
//str.insert(pos,str) 在字符串str的pos位置处插入字符串str1
str.insert(0, str1); //str = "士兵们,献出心脏!!!"
cout << str << endl;
//str.insert(pos,str,n,m) 在字符串str的pos位置处插入字符串str2的n位置到m位置的字符串
str.insert(15, str2, 1, 6); //str = "士兵们,献出心脏塔塔开!!!"
cout << str << endl;
//str.insert(pos,c_str,n) 在字符串str的pos位置处插入字符数组c_str从0开始后面n个字符
str.insert(24, "~~~~~~", 3); //str = "士兵们,献出心脏塔塔开!!!~~~"
cout << str << endl;
虽然有这样的接口,但是我们知道对于类似于顺序表的结构来说,这样的插入,实际上底层都会将后面的数据进行挪动,因此效率难免会低一些。
2.7、替换(replace):
替换与插入对应,对比理解更为简单。
//str.replace()
string str("hello world");
string str1("aaaa");
string str2("it's a funny day");
//str.replace(p0,n0,n,ch) 删除p0开始的n0个字符,然后在p0处插入n个字符ch
str.replace(0, 5, 5, 'h'); //str = "hhhhh world"
cout << str << endl;
//str.replace(p0,n0,str) 删除p0开始的n0个字符,然后在p0处插入字符串str
str.replace(0, 5, str1); //str = "aaaa world"
cout << str << endl;
//str.replace(p0,n0,str,pos,n) 删除p0开始的n0个字符,然后在p0的位置插入字符串str从pos位置开始的n个字符
str.replace(0, 5, str2, 0, 13); //str = "it's a funny world"
cout << str << endl;
//str.replace(p0,n0,cstr,n) 删除p0开始的n0个字符,然后在p0的位置插入字符串从0开始的n个字符
str.replace(0, 7, "how beautiful day", 4); //str = "how funny world"
cout << str << endl;
2.8、追加(append、push_back、+=):
append函数用在字符串的末尾添加字符和字符串。(同样与插入、替换对应理解)而push_back只适用于添加单个字符,此外,对于添加来说,如果是在末尾添加字符或者字符串我们仍然可以像初始化中的拷贝构造一样,即通过+=进行添加。
//str.append() / str.push_back / operate+=
string str("hello world");
string str1("wasd");
string str2("wasdqwer");
//str.append(n,ch) 在字符串str结尾添加n个字符ch
str.append(5, 's'); // str = "hello worldsssss"
cout << str << endl;
//str.append(str1) 在字符串str结尾添加字符串str1
str.append(str1); //str = "hello worldssssswasd"
cout << str << endl;
//str.append(str1,n,m) 在字符串str结尾添加字符串str
str.append(str2, 4, 7); //str = "hello worldssssswardqwer"
cout << str << endl;
//str.append(cstr,n) 在字符串str结尾添加字符数组cstr前n个字符
str.append("aaaaaaaa", 4); //str = "hello worldssssswardqweraaaa"
cout << str << endl;
//str.push_back()
string str3("aaaa");
str3.push_back('b');
cout << str3 << endl;
//+=
str3 += "daddw";
cout << str3 << endl;
2.9、赋值(assign):
![](https://img-blog.csdnimg.cn/a1508dcc20f645fab7ea5cdebabe4dc8.png)
赋值也是一种初始化方法,与插入、替换、添加对应理解较为简单。
string str;
string tmp = "Now I began the Death The Destroyer of the world";
//str.assign(n,ch) 将10个字符ch赋值给字符串str
str.assign(10, 's'); //str = "sssssssss"
cout << str << endl;
//str.assign(str1) 将字符串str1赋值给str
str.assign(tmp); //str = "asda"
cout << str << endl;
//str.assign(str1,n,m) 将字符串str1从n到m个字符赋值给str
str.assign(tmp, 0, 21); //str = "Now I began the Death"
cout << str << endl;
//str.assign(cstr,n) 将字符数据的前n个字符赋值给str
str.assign("The Destroyer of the world", 20);
cout << str << endl;
2.10、删除与判空(erase、clear、empty):
对于clear,其具有清空的功能,也就是从任意的字符串使用clear,都会将其清空至空字符串;对于erase来说,其可以指定的删除,也就是说可以删除一部分,也可以删除全部。
string str = "welcome to the world";
//str.erase(pos,n) 删除字符串str从pos位置开始的n个字符
str.erase(11, 4); //str = "welcome to world"
cout << str << endl;
str.clear();
cout << str << endl; //str = ""
str.empty(); //返回值为1
cout << str.empty() << endl;
clear()实际上不会将capacity的空间也清除掉,即size会改变,但capacity并不会改变。
对于empty来说,实际上是判断是否为空的函数,是空就返回1,不是空就返回0,因此empty是根据size()是否为0判断的。
2.11、剪切(substr):
//str.substr()
string str = "Now I began the Death The Destroyer of the world";
//str.substr(pos,n) //得到字符串str的pos位置后面的n个字符
string s1 = str.substr(0, 21); //str = "Now I began the Death"
cout << s1 << endl;
//str.substr(pos) //得到字符串str的pos位置后的字符
string s2 = str.substr(23); //str = "The Destroyer of the world"
cout << s2 << endl;
string s3 = str.subst
2.12、比较(compare):
两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇’\0’为止。若是遇到‘\0’结束比较,则长的子串大于短的子串,如:“9856” > “985”。如果两个字符串相等,那么返回0,调用对象大于参数返回1,小于返回-1。
string str1 = "small leaf";
string str2 = "big leaf";
//s.compare(str) 比较当前字符串s和str的大小
cout << str1.compare(str2); // 1
//s.compare(pos,n,str) 比较当前字符串s从pos开始的n个字符与str的大小
cout << str1.compare(2, 7, str2); // -1
//s.compare(pos,n0,str,pos2,n) 比较当前字符串s从pos开始的n0个字符与str中pos2开始的n个字符组成的字符串的大小
cout << str1.compare(6, 4, str2, 4, 4); // 0
//s.compare(pos,n0,cstr,n) 比较当前字符串s从pos开始的n0个字符与字符数组cstr中前n个字符的大小
//此处不可将"big leaf"替换为str2
cout << str1.compare(6, 4, "big leaf", 4); // 1
2.13、交换(swap):
string str1 = "small leaf";
string str2 = "big leaf";
//或者str1.swap(str2) ,输出结果相同
swap(str1,str2); // str1 = "big leaf" str2 = "small leaf"
swap(str1[0],str1[1]); // str1 = "ibg leaf"
2.14、反转(reverse):
string str1 = "Now I began the Death The Destroyer of the world";
cout << str1 << endl;
string::iterator it1 = str1.begin();
string::iterator it2 = str1.end();
reverse(it1, it2);
cout << str1 << endl;
2.15、迭代器(iterator):
迭代器实际上是一个像指针一样的东西,这是对行为来说的。需要注意的是,这里不能用char*,虽然对于vs这个平台可以使用char*,但难保其他的平台是char*,因此迭代器的底层不一定是char*,这在模拟实现中将会详细介绍。(对于迭代器来说,只要会用string类,那么vector、list等容器与其方法是一样的)
2.15.1、正向迭代器:
//迭代器 -- 通用的访问形式
string s1("1234");
string::iterator it1 = s1.begin();//s.begin() 返回字符串s第一个字符的位置
while (it1 != s1.end())//s.end() 返回字符串s最后一个字符串的后一个位置
{
*it1 += 1;
++it1;
}
it1 = s1.begin();
while (it1 != s1.end())
{
cout << *it1 << " ";
++it1;
} // 输出:2 3 4 5
2.15.2、反向迭代器:
//反向迭代器
string::reverse_iterator rit = str.rbegin();
while (rit != str.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
此外,我们发现此定义名过长,因此我们也可以用auto去定义变量rit接收rbegin()。
2.15.3、const迭代器:
当我们需要只读的时候,为了避免改变其中的值,在迭代器使用时我们就会选择const迭代器,顾名思义const迭代器能够保护迭代指向的变量不被改变,那我们实际来看一下const迭代器如何使用:
void Print(const string& s)
{
string::const_iterator it = s.begin();//正向
while (it != s.end())
{
cout << *it << " ";
it++;
}
cout << endl;
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("1234");
Print(s1);
return 0;
}
总结:迭代器经过上述的描述,一共有四种,即正向、反向、正向const、反向const,其不能混合定义,否则会出现错误。
2.16、查找(find):
2.16.1、find:
string str = "/Now I began the Death . The Death of the world/";
string str1 = "Death";
//str.find(str1) 查找字符串str1在当前字符串str中第一次出现的位置
int pos1 = str.find(str1);
cout << pos1 << endl; //17
//str.find(str1,pos) 查找字符串str1在当前字符串str的[pos,end]中第一次出现的位置
int pos2 = str.find(str1, 20);
cout << pos2 << endl; //29
//str.find(cstr,pos,n) 查找字符数组cstr前n的字符在当前字符串s的[pos,end]中第一次出现的位置
int pos3 = str.find("Death",0,2);
cout << pos3 << endl; //17
//str.find(ch,pos) 查找字符ch在当前字符串s的[pos,end]中第一次出现的位置
int pos4 = str.find('D', 0);
cout << pos4 << endl; //17
2.16.2、rfind:
rfind与find的区别就是:find是从左往右找(即从前往后),而rfind是从后往前找。
string str = "The apple thinks apple is delicious"; //长度34
string key = "apple";
//s.rfind(str) 查找字符串str在当前字符串s中最后一次出现的位置
int pos5 = str.rfind(key); // 17
//s.rfind(str,pos) 查找字符串str在当前字符串s的[0,pos+str.length()-1]中最后一次出现的位置
int pos6 = str.rfind(key, 16); // 4
//s.rfind(cstr,pos,n) 查找字符数组cstr前n的字符在当前字符串s的[0,pos+n-1]中最后一次出现的位置
//此处不可将"apple"替换为key
int pos7 = str.rfind("apple", 40, 2); // 17
//s.rfind(ch.pos) 查找字符ch在当前字符串s的[0,pos]中最后一次出现的位置
int pos8 = str.rfind('s', 30); // 24
2.16.3、find_xxx_of()函数(功能强大,但不常用):
1.find_first_of:
// s.find_first_of(str) 查找字符串str中的任意字符在当前字符串s中第一次出现的位置
int pos1 = str.find_first_of(key); // 2
//s.find_first_of(str,pos) 查找字符串str中的任意字符在当前字符串s的[pos,end]中第一次出现的位置
int pos2 = str.find_first_of(key, 10); // 11
//s.find_first_of(cstr,pos,n) 查找字符串str前n个任意字符在当前字符串s的[pos,end]中第一次出现的位置
//此处不可将"aeiou"替换为key
int pos3 = str.find_first_of("aeiou", 7, 2); // 17
//s.find_first_of(ch,pos) 查找字符ch在当前字符串s的[pos,end]中第一次出现的位置
int pos4 = str.find_first_of('r', 0); // 6
即找到每个str中的字符进行替换,与find和rfind的区别是:此查找找的是字符串中的所有字符,而不是字符串。下面的几个同样如此:
2.find_last_of:
//s.find_last_of(str) 查找字符串str中的任意字符在当前字符串s中最后一次出现的位置
int pos1 = str.find_last_of(key); // 27
//s.find_last_of(str,pos) 查找字符串str中的任意字符在当前字符串s的[0,pos]中最后一次出现的位置
int pos2 = str.find_last_of(key, 15); // 11
//s.find_last_of(cstr,pos,n) 查找字符串str前n个任意字符在当前字符串s的[0,pos]中最后一次出现的位置
//此处不可将"aeiou"替换为key
int pos3 = str.find_last_of("aeiou", 20, 2); // 17
//s.find_last_of(str) 查找字符ch在当前字符串s的[0,pos]中最后一次出现的位置
int pos4 = str.find_last_of('r', 30); // 28
3.find_first_not_of:
//s.find_first_not_of(str) 查找字符串str之外的任意字符在当前字符串s中第一次出现的位置
int pos1 = str.find_first_not_of(key); // 0
//s.find_first_not_of(str,pos) 查找字符串str之外的任意字符在当前字符串s的[pos,end]中第一次出现的位置
int pos2 = str.find_first_not_of(key, 10); // 10
//s.find_first_not_of(cstr,pos,n) 查找字符串str前n个之外任意字符在当前字符串s的[pos,end]中第一次出现的位置
//此处不可将"aeiou"替换为key
int pos3 = str.find_first_not_of("aeiou", 7, 2); // 7
//s.find_first_not_of(str) 查找字符ch之外任意字符在当前字符串s的[pos,end]中第一次出现的位置
int pos4 = str.find_first_not_of('r', 0); // 0
4.find_last_not_of:
//s.find_last_not_of(str) 查找字符串str之外的任意字符在当前字符串s中最后一次出现的位置
int pos1 = str.find_last_not_of(key); // 29
//s.find_last_not_of(str,pos) 查找字符串str之外的任意字符在当前字符串s的[0,pos]中最后一次出现的位置
int pos2 = str.find_last_not_of(key, 15); // 15
//s.find_last_not_of(cstr,pos,n) 查找字符串str前n个之外任意字符在当前字符串s的[0,pos]中最后一次出现的位置
//此处不可将"aeiou"替换为key
int pos3 = str.find_last_not_of("aeiou", 20, 2); // 20
//s.find_last_not_of(str) 查找字符ch之外任意字符在当前字符串s的[0,pos]中最后一次出现的位置
int pos4 = str.find_last_not_of('r', 30); // 29
3、string类的应用:
3.1、三种遍历方式:
int main()
{
string s1("1234");
// 遍历他
// 1、下标 []
for (size_t i = 0; i < s1.size(); ++i)
{
s1[i]++;//实际上这是operator[]()的运算符重载
}
//s1[10];
cout << s1 << endl;
// 2、范围for
for (auto& ch : s1)
{
ch--;
}
cout << s1 << endl;
// 反转一下
size_t begin = 0, end = s1.size() - 1;
while (begin < end)
{
swap(s1[begin++], s1[end--]);
}
cout << s1 << endl;
//reverse(s1.begin(), s1.end()); 算法头文件中的函数,直接使用即可。
cout << s1 << endl;
// 3、迭代器 -- 通用的访问形式
string::iterator it1 = s1.begin();
while (it1 != s1.end())
{
*it1 += 1;
++it1;
}
it1 = s1.begin();
while (it1 != s1.end())
{
cout << *it1 << " ";
++it1;
}
cout << endl;
}
3.2、替换空格:
替换空格https://leetcode.cn/problems/ti-huan-kong-ge-lcof/solutions/
解法一:
非常常规的思路,用find函数找出字符串中空格的位置,并用replace函数将该位置的空格改为%20。
class Solution {
public:
string replaceSpace(string s) {
size_t pos = s.find(' ');
while(pos != string::npos)
{
s.replace(pos,1,"%20");
pos = s.find(' ',pos + 3);
}
return s;
}
};
解法二:
我们还可以新建一个string的空字符串,遍历传入的string s如果没有碰到空格就+=该字符,碰到了空格就+=%20
class Solution {
public:
string replaceSpace(string s) {
string ret;
for(auto ch : s)
{
if(ch != ' ')
{
ret+=ch;
}
else
{
ret+="%20";
}
}
return ret;
}
};
3.3、find取后缀:
如果我们想确定一个文件是什么类型,我们需要知道其后缀,最后一个.后面的名字就是其后缀,那我们就可以先通过rfind()找到最后一个.的位置,再通过substr拷贝下来,这样就获得了相应文件的后缀了。(找最后一个.是由于在Linux下的文件可能存在类似于test.cpp.zip.tar的文件名,而其文件类型其实是tar)
void test_string11()
{
string file;
cin >> file;
size_t pos = file.rfind('.');
if (pos != string::npos)
{
string suffix = file.substr(pos);
cout << suffix << endl;
}
}
3.4、getline应用:
这道题如果我们直接用scanf或者cin的话,对于输入hello nowcoder来说,实际上输入的只有hello,因为到空格就截止了,因此这里就需要了getline输入,getline的作用是到换行符时才结束输入。那我们看看代码实现:
#include <iostream>
using namespace std;
int main() {
string s;
getline(cin,s);
size_t pos = s.rfind(' ');
cout<<s.size()-pos-1<<endl;
}