概述
string类型是c++的字符串类型,其继承自basic_string类。
使用string需要导入头文件#include <string>,并且在命名空间std下。
c++string是否是写时复制? (像Qt的string一样)?经过自己的测试,推断,c++的方式不是写时复制,其在赋值的阶段就会开辟新的空间。
1. 构造string类对象
std::string str1("Hello World");
std::string str2;std::string str3(20, '-'); // str3包含20个中划线
std::string str4({ "Hello" }); // 使用初始化列表std::string str5(str1, 5); // 拷贝从str1第5个字符开始后面的字符
std::cout << str5 << std::endl; // 输出: World(注意W前面有个空格字符)const char* str = "Jack";
std::string str6(str, 2); // 拷贝str字符串的前两个字符
std::cout << str6 << std::endl; // 输出:Ja
2. string的访问
- 使用下标
std::cout << str1[0] << std::endl; // 输出: H 使用下标运算符访问第一 个字符,注意越界
std::cout << str1.at(0) << std::endl; // 和下标运算符类似- 使用back()和front()
std::cout << str1.back() << std::endl; // 输出d,返回最后一个字符
str1.back() = 'e'; // 可以通过back()返回修改对应位置 字符
std::cout << str1 << std::endl; // Hello Worlefront()的使用方式类似。
- 使用迭代器 -- 其它迭代器的使用和前面容器是类似的
std::string str1("Hello World");
for (auto i : str1) {
std::cout << i << " "; // H e l l o W o r l d
}这是c++11新增的for循环,可以对拥有迭代器的类方便遍历,当然使用迭代器还可以像之前容器那样去遍历(不了解可以去容器章节查看),当然那样写起来比较麻烦。
3. string的长度
- std::string str1("Hello World");
std::cout << str1.length() << std::endl; // 11
std::cout << str1.size() << std::endl; // 11
std::cout << strlen(str1.c_str()) << std::endl; // 11使用strlen需要导入头文件#include <string.h> 或者 #include <cstring>
注意:
c++所有计算字符串长度的函数,计算出的都是字符串的字节数。也就是所如果我们去计算"我爱你"这样的中文字符串的长度,上面的函数都会返回6。因为在程序中一个中文一般占两个字节。
4. c++字符串转化为C语言字符串
在使用一些函数的时候,尤其是c的函数,我们需要传入的字符串是C语言的字符串类型:char*,或者是const char*。
这时候我们可以通过c_str()函数将c++字符串转化为C语言字符串。str.c_str(); 其会返回一个const char*的字符串指针。
为什么呢?string是c++关于处理字符串封装的一个类,简单来看,其内部就是存在一个char*的指针,然后内部会为它动态开辟空间。并且类中包含了一系列处理字符串的函数。
你使用c_str()它只是将这个指针给你返回回来了。
5. 字符串的容量
std::string str1;
str1.reserve(20); // 开辟20个空间
std::cout << str1.capacity() << std::endl; // 打印string目前的容量
容量是指当前字符串对应的空间有多大,它并不等于字符串的长度。
它会根据内部的算法,进行空间的动态扩充,reserve是指定string开辟20个空间,但是实际打印出来并不是,因为它会根据内部的算法进行扩充。(感觉和vector容器是一样的)
6. 字符串的查找
- find()
此函数可以用来查找string中存放字符串的特定字符和字串,并且可以指定查找位置。- 成功找到: 返回找到的第一个字符或者首字符的下标
没有找到: 返回npos
如果指定查找的范围超出字符串的范围: 结果未定义std::string str1("Hello World");
using size = std::string::size_type; // 给strng内置的类型取一个别名,方便使用size ret = str1.find("He"); // 查找子串"He",找到返回字段首字符的下 标,没有找到返回npos
std::cout << ret << std::endl;size ret1 = str1.find("He", 5); // 从下标为5的位置开始查找子串"He"找到返 回字段首字符的下标,没有找到返回npos
size ret2 = str1.find('c'); // 查找字符c是否存在,存在返回下标找到返 回字段首字符的下标,没有找到返回npos
std::cout << ret2 << std::endl;size ret3 = str1.find("W", 5); // 从下标为5的位置开始,查找字符‘w’是 否存在,找到返回字段首字符的下标,没 有找到返回npos
std::cout << ret3 << std::endl;
rfind()就是从反方向查找,用法和find()类似
find_first_of() 查找第一次出现的子串或者字符
std::string str1("Hello World");
str1.find_first_of('H');
str1.find_first_of('H',6); // 从下标为6的位置开始查找
str1.find_first_of("He",6); // 从下标为6的位置开始查找
str1.find_first_of("He");
- find_last_of() 查找最后一个出现的子串或者字符 -- 用法和上面的一样
- find_first_not_of() 查找第一个出现的与查找子串或者字符不匹配的下标
size_t ret = str1.find_first_not_of('s'); // 第一个与s不匹配的下标为0
- find_last_not_off() 和first相反
注意: 1. 如果查找的是子串,那么必须整个串都一样,否则就是不匹配
2. 后面的四种方法也可以使用npos来判断是否查找成功
6.1 如何判断查找成功和失败
我们上面说到,查找成功返回对应子串或者字符的首字符下标,查找失败返回npos。npos最开始被定义为-1,但是其并不是一直是-1,所以find()查找失败不一定都返回-1。
看下面代码:std::string str1("Hello World");
using size = std::string::size_type;size ret = str1.find('s'); // 's'字符在字符串中没有,所以其肯定找不到
if (ret == str1.npos) { // 输出: 没有找到
std::cout << "没有找到" << std::endl;
}
else {
std::cout << str1[ret] << std::endl;
}
所以判断是否查找失败,我们可以使用其返回值(返回值类型可以是string内置的size_type也可以直接使用size_t)与npos进行比较,如果等于npos就是没有找到,找到就返回所在下标。
7. 判断string是否为空
- str1.empty(); // 为空返回true,不为空返回false;
8. string的比较
- 直接使用比较运算符进行比较 -- 比较规则和C语言字符串类似
std::string str1("Hello World");
std::string str2("Hello");if (str2 < str1) {
std::cout << "str2小于str1" << std::endl;
}- 使用compare()函数进行比较
std::string str1("Hello World");
std::string str2("Hello");
// str1和str2整体进行比较,相同返回0,str1小返回-1,str1大返回+1
str1.compare(str2);
// str1下标为[0,5)字符组成的字符串与str2进行比较,返回值和上面一样
int ret = str1.compare(0,5,str2);
// str1下标为[0,5)字符组成的字符串与str2下标为[0,5)的字符组成的字符串进行比较
int ret2 = str1.compare(0, 5, str2, 0, 4);注意: 使用compare()可以指定比较两个字符串对应区间内的字符串,但是如果我们指定的区间大于了原来字符串的长度,那么就默认使用字符串的size(),也就是字符串的长度。
举例:int ret = str1.compare(0,20,str2); // 我们这里指定的是str1的[0,20)的字符组成的字符串与str2进行比较,但是我们str1的长度是11,明显20超出了str1的长度,这时候compare()函数就会使用str1.size()也就是str1的长度范围的字符串,也就是[0,11),也就是整个字符串。
9. string的添加
- insert()函数
std::string str1("Hello World");
std::string str2("编程");
str1.insert(5, "开心"); // 在下标为5的位置插入"开心"
str1.insert(0, "美丽"); // 在下标为0的位置插入"美丽"
str1.insert(str1.size(), "漂亮"); // 在字符串的尾部添加"漂亮",因为尾部下标就是 字符串的长度str1.insert(str1.begin(),'z'); // 在字符串开头的位置插入字符'z'
str1.insert(str1.end(), 'z'); // 在字符串尾部的位置插入字符'z'str1.insert(str1.begin(), 5, 'a'); // 在尾部添加5个a,首部也类似
str1.insert(0, str2); // 在下标为0的位置添加str2std::cout << str1 << std::endl;
使用append()函数对字符串进行尾部添加
str1.append("123"); // 尾部添加字符串"123"
str1.append(str2); // 尾部添加字符串str2直接使用+运算符进行拼接
str1 += "123"; // 尾部拼接
str1 = "123" + str1; // 首部拼接
注意: 使用+直接拼接必须有一个string类对象,不能直接对C语言的字符串进行+,因为那都是地址。
10. string的删除
使用下标 -- 删除对应下标以及之后的所有数据或者相应数量的数据
返回值: 返回删除后的string对象
std::string str1("Hello World");
std::string str2;str2 = str1.erase(2); // 删除下标为2开始的所有字符
std::cout << str1 << std::endl;
std::cout << str1 << std::endl; // 输出和str1是一样的
str1.erase(2,3); // 删除从下标为2开始的三个字符,同理也是 返回删除后的string对象(和上面是一样的)
std::cout << str1 << std::endl;
使用迭代器 -- 删除迭代器指向数据,或者迭代器范围的数据
str1.erase(str1.begin()); // 删除第一个字符,迭代器end()用于删除最后一个
返回值: 返回指向删除字符或者字符串的后一个位置字符的迭代器, 如果删除的字符后面已经没有字符了,返回end()迭代器(就是 指向最后一个元素的下一个位置的迭代器)
std::cout << str1 << std::endl;
str1.erase(str1.begin(),str1.begin()+1); // 删除迭代器范围的字符[beg,end)
std::cout << str1 << std::endl;注意: 还是和之前容器所说的一样,使用erase结合循环删除特定元素的时候,应该注意下一个迭代器的位置是erase()的返回值。(具体可以看之前容器部分)
11. string字符串的替换
- std::string str1("Hello World");
std::string str2("Good Good Study");
str1.replace(1, 2, "123"); // 从下标为1的字符开始,共计两个字节替换成"123"
str1.replace(1, 2, str2);
std::cout << str1 << std::endl;
str1.replace(str1.begin(), ++str1.begin(), "你好"); // 将迭代器范围[beg,end)内的字 符替换成"你好"
str1.replace(str1.begin(), ++str1.begin(), str2);
std::cout << str1 << std::endl;
str1.replace(0, 2, str2, 0, 3); // 将str1从下标为0的字符开始,共计两个字符替换 成str2从下标为0开始,共计3个字符的字符串
std::cout << str1 << std::endl;
str1.replace(3, 2, "Day Day Up", 2);
std::cout << str1 << std::endl; // 将str1下标为3的字符开始,共计两个字符替换成 后面字符串的前2个字符
str1.replace(str1.begin(), ++str1.begin(), "我们", 2);
std::cout << str1 << std::endl;返回值: 其返回的就是替换之后的string对象
12. 使用assign()函数进行赋值
使用方法和前面的方法是类似的,每次使用assign给字符串赋值的时候,都会覆盖原来字符串的值。
std::string str1;
std::string str2("ch");
str1.assign(5, 'a'); // aaaaa
str1.assign("123"); // 123
str1.assign("123",2); // 赋值字符串的前两个字符 12
str1.assign(str2); // ch
str1.assign(str2,1); // 使用str2下标为1的字符赋值str1 h
13. resize()重新设置字符串长度
和容器中resize()的用法是类似的。
接收两个参数,第一个参数是一个整数,表示长度变成多少
第二个参数是一个字符,用于指定长度变长的时候填充的字符,默认以空格 填充
我们通过第一个参数可以指定扩充或者缩短字符串的长度,第二个参数只有扩充长度的时候才有效。
std::string str1;
std::string str2("ch");
str1.resize(5,'a');
std::cout << str1 << std::endl; // aaaaastr2.resize(1);
std::cout << str2 << std::endl; // c
14. string类型转换为其它类型
- std::stoi(); 将字符串转换为int类型
std::stol(); 将字符串转换为long类型
std::stoll(); 将字符串转换为long long类型- std::stoul(); 将字符串转换为unsigned long类型
std::stoull(); 将字符串转换为unsigned long long类型- std::stof(); 将字符串转换为float类型
std::stod(); 将字符串转换为double类型
std::stold(); 将字符串转换为long double类型例子: 用法都类似
std::string str1("123");
std::cout << std::stoi(str1) << std::endl; // 123std::string str2("123abc");
std::cout << std::stoi(str2) << std::endl; // 123
15. to_string()
返回一个string类型,将别的类型转换为字符串类型。
int a = 5;
std::string str = std::to_string(a);
16.substr()字符串分割
只是简单的字符串分割。如果要指定分割符分割的话,可以使用前面说的strtok()函数
std::string str("Hello World");
std::string ret = str.substr(0,5); // 从下标0开始分割5个字符
std::cout << ret << std::endl; // Hello
std::cout << str << std::endl; // Hello World
注意: 关于string的操作其实都是以字节为准的 (可以跳过)
我们在上面的计算字符串长度,从指定位置查找字符或者子串,删除指定位置的字符,删除指定个数的字符,以及使用replace()替换指定位置的字符,这写都是针对于字节来说的。
指不过上面的操作,都是以英文字符的形式去展示的,英文字符一个字符占用一个字节,所以上面我们都使用字符数表示了,其实不是这样的,实际那些函数操作的是字节。
如果,在例子中使用中文字符(一个字符占两个字节):1. 那么计算的长度就是字节数: 字符数*2
2. str1.erase(1); // 对于中文字符会出现乱码,因为此处的1表示,删除字符串1字节之后的所有字节,中文字符一个占用两个字节,如果你将1字节后面的字节都删除了,表示中文字符的二进制就被截断了,就会出现乱码
str.erase(2); // 这样表示删除字符串2字节之后的所有字符,对于中文字符,这样才是真正的保留下第一个字符。
3. 对于迭代器也是同理,begin()和end()都是表示一个字节。
4. str1.replace(0, 2, str2, 0, 3); // 这个取代语句,是将str1第一个位置开始两个字节的元素替换成str2第一个位置开始三个字节的元素。
如果我们操作的是中文,那么替换的字节数必须是2的倍数,否则就会出现乱码。
等等,相关的操作其实都是对于字节数的操作。其实这些你不知道也无所谓,只是如果你去尝试操作放有中文的或者其它多字节字符的字符串的时候,可能会感到蒙。