赞
踩
string是C++风格的字符串,本质上是一个类,它的内部封装了char *
,是一个char *
型的容器
使用string需要包含<string>
头文件
string的内部封装了很多方法,本文将重点介绍string的一些常用方法,这些方法足以应对日常的刷题和比赛
#include <iostream>
#include <string>
using namespace std;
int main() {
string str;
cout << "str = " << str << endl;
return 0;
}
#include <iostream>
#include <string>
using namespace std;
int main() {
char words[15] = {"Hello World"};
string str(words);
cout << "str = " << str << endl;
return 0;
}
string类重载了赋值符号
string的赋值本质上是将数据源的内容拷贝一份放到string里,修改string的值并不会影响数据源
#include <iostream> #include <string> using namespace std; int main() { string str; char words[15] = {"Hello"}; str = words; cout << "str = " << str << endl; str[0] = 'h'; cout << "str = " << str << endl; cout << "words = " << words << endl; return 0; }
输出结果为
str = Hello
str = hello
words = Hello
#include <iostream>
#include <string>
using namespace std;
int main() {
string src = "Hello World";
string destination;
destination = src;
cout << "destination = " << destination << endl;
return 0;
}
先看以下代码
#include <iostream>
#include <string>
using namespace std;
int main() {
string src = "Hello World";
string destination = src;
cout << "destination = " << destination << endl;
return 0;
}
虽然代码string destination = src;
看起来是src为destination赋值,但是经过编译器优化后,该句代码等价于string destination(src);
,也就是说,该句代码本质上是调用了string类的构造方法,而不是赋值操作
我们可以使用自定义的类来验证
#include <iostream> using namespace std; class Student { public: int age; double height; public: Student() { this->age = 0; this->height = 0.0; } Student(const Student &anotherStudent) { cout << "拷贝构造函数" << endl; this->age = anotherStudent.age; this->height = anotherStudent.height; } Student &operator=(const Student &anotherStudent) { cout << "重载赋值符号" << endl; this->age = anotherStudent.age; this->height = anotherStudent.height; return *this; } }; int main() { Student tom; tom.age = 18; tom.height = 180; Student jerry = tom; cout << "jerry.age = " << jerry.age << endl; cout << "jerry.height = " << jerry.height << endl; Student amy; amy = tom; cout << "amy.age = " << amy.age << endl; cout << "amy.height = " << amy.height << endl; return 0; }
输出结果为
拷贝构造函数
jerry.age = 18
jerry.height = 180
重载赋值符号
amy.age = 18
amy.height = 180
string类重载了[]符号
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "Hello,World";
cout << "str[0] = " << str[0] << endl;
str[0] = 'h';
cout << "str[0] = " << str[0] << endl;
return 0;
}
string类重载了+和+=符号,可以接收字符数组、字符、string、字符串等类型的变量
#include <iostream> #include <string> using namespace std; int main() { string str01; char words[15] = {"Hello"}; str01 += words; cout << "str01 = " << str01 << endl; char ch = ','; str01 += ch; cout << "str01 = " << str01 << endl; string temp = "World"; str01 += temp; cout << "str01 = " << str01 << endl; str01 += "!"; cout << "str01 = " << str01 << endl; string str02; str02 = str02 + words + ch + temp + "!"; cout << "str02 = " << str02 << endl; return 0; }
输出结果为
str01 = Hello
str01 = Hello,
str01 = Hello,World
str01 = Hello,World!
str02 = Hello,World!
string类的find方法有多个重载版本,我们只学习它的基本用法
find方法可以接收string、字符、字符数组三种类型的参数
#include <iostream> #include <string> using namespace std; int main() { string str = "Hello,World Hello,World! Hello,World!"; string temp = "Hello"; char ch = ','; char words[15] = "World"; int index = str.find(temp); cout << "index = " << index << endl; index = str.find(ch); cout << "index = " << index << endl; index = str.find(words); cout << "index = " << index << endl; return 0; }
输出结果为
index = 0
index = 5
index = 6
上述的find方法只接收一个参数,默认是从下标0开始寻找子串
如果我们想从特定的位置开始寻找子串,我们可以填入开始寻找的下标作为find方法的第二个参数
配合循环,我们就可以在母串中找到所有子串的开始位置
#include <iostream> #include <string> using namespace std; int main() { string str = "Hello,World Hello,World! Hello,World!"; int start = 0; while (true) { int index = str.find("Hello", start); if (index == -1) { break; } cout << "index = " << index << endl; start = index + 5; } return 0; }
输出结果为
index = 0
index = 12
index = 25
当使用string类的find方法查找不到子串时,find方法将会返回string::npos
string::npos
的类型本质上是unsigned long long,而且string::nops
的数位(二进制)都是1,也就是说string::npos
的值为264-1,即18,446,744,073,709,551,615
当我们用int、long、long long等有符号整形变量去接收string::npos
时,该有符号整形变量的数位(二进制)将全变成1,而-1的补码也全部为1,所以该有符号整形变量的值也就变成了-1
因此我们可以根据find方法是否返回-1或string::npos
来判断是否寻找到子串
#include <iostream> #include <string> using namespace std; int main() { cout << "string::npos = " << string::npos << endl; string str = "Hello,World"; if (-1 == str.find("Tom")) { cout << "Can not find subStr Tom" << endl; } if (string::npos == str.find("Jerry")) { cout << "Can not find subStr Jerry" << endl; } return 0; }
输出结果为
string::npos = 18446744073709551615
Can not find subStr Tom
Can not find subStr Jerry
find方法的底层并没有采用KMP算法等更高效的字符串匹配算法
find方法使用的是朴素的线性搜索算法,也就是逐个字符比对,时间复杂度大约为 O(n*m) ,其中 n 为母串长度,m 为子串长度。
string的替换主要是使用replace方法,replace方法也有很多个重载版本,我们只学习它的基本用法
常规的replace方法接收三个参数
#include <iostream> #include <string> using namespace std; int main() { string str = "Hello,World"; string str01 = "hello"; char str02[15] = "world"; str.replace(0, str01.length(), str01); cout << "str = " << str << endl; str.replace(6, 5, str02); cout << "str = " << str << endl; return 0; }
输出结果
str = hello,World
str = hello,world
string类重载了>=、<=、>、<、!=、==
符号,使得string类在进行字符串比较时可以接收char数组类型或string类型的参数
string类的比较是将两个字符串的字符从左到右逐个比较(比较的依据是字符的ASCII码值),直到两个字符串出现不同的字符或者两个字符串中的每个字符都比较完之后返回
string类在进行字符串的比较时会返回三个值
#include <iostream>
#include <string>
using namespace std;
int main() {
string str01 = "Hello,World";
string str02 = "hello,World";
char str03[20] = "Hello,world";
cout << (str01 == str02) << endl;
cout << (str01 > str03) << endl;
cout << (str02 > str03) << endl;
return 0;
}
输出结果
0
0
1
使用string类的length方法或size方法可以得到string的长度
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "Hello,World";
cout << "str.length() = " << str.length() << endl;
cout << "str.size() = " << str.size() << endl;
return 0;
}
输出结果
str.length() = 11
str.size() = 11
string的插入使用的是insert方法,insert方法也有很多个重载版本,我们只学习它的基本用法
常规的insert方法接收两个参数
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "Hello";
str.insert(5, "World");
cout << str << endl;
return 0;
}
输出结果为
HelloWorld
string的插入使用的是erase方法,该方法主要有两个版本
第一个版本接收两个参数
第二个版本只接收一个参数,该参数为起始坐标,使用该版本将会清空起始位置及之后的所有字符
#include <iostream> #include <string> using namespace std; int main() { string str01 = "HelloWorld!"; str01.erase(5, 5); cout << str01 << endl; string str02 = "HelloWorld!"; str02.erase(5); cout << str02 << endl; return 0; }
输出结果为
Hello!
Hello
string的子串获取主要使用substr方法,该方法接收两个参数
如果省略第二个参数,将会截取从起始位置开始到字符串结尾的整个子串
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "HelloWorld!";
cout << "str.substr(5, 5) = " << str.substr(5, 5) << endl;
cout << "str.substr(5) = " << str.substr(5) << endl;
return 0;
}
输出结果为
str.substr(5, 5) = World
str.substr(5) = World!
注意:以下介绍的string与基本数据类型之间相互转换的方法是在C++11之后才出现的,在C++11之前的版本中使用以下方法程序将会报错
基本数据类型转string使用的是to_string方法
#include <iostream> #include <string> using namespace std; int main() { int number = 8848; long long date = 20202020; double pi = 3.1415926; string NUMBER = to_string(number); string DATE = to_string(date); string PI = to_string(pi); cout << "NUMBER = " << NUMBER << endl; cout << "DATE = " << DATE << endl; cout << "PI = " << PI << endl; return 0; }
输出结果为
NUMBER = 8848
DATE = 20202020
PI = 3.141593
这里主要介绍string转换成int、long long、double,string,转成其它基本数据类型的方法可以类比
#include <iostream> #include <string> using namespace std; int main() { string intStr = "8848"; string timeStr = "202002202020"; string piStr = "3.1415926"; cout << "stoi(intStr) = " << stoi(intStr) << endl; cout << "stoll(timeStr) = " << stoll(timeStr) << endl; cout << "stod(piStr) = " << stod(piStr) << endl; return 0; }
输出结果为
stoi(intStr) = 8848
stoll(timeStr) = 202002202020
stod(piStr) = 3.14159
当浮点数的总长度大于等于7(包括小数点)时,C/C++程序输出浮点数的格式会千奇百怪,下面介绍两种格式化输出浮点数的方法
我们可以借助<iomanip>
头文件来控制浮点数的输出格式
#include <iostream> #include <string> #include <iomanip> using namespace std; int main() { string piStr = "3.1415926"; double pi = stod(piStr); cout << "************" << endl; // 12个* cout << setw(12) << setfill('\0') << setprecision(8) << fixed << setiosflags(ios::left) << pi << endl; cout << setw(12) << setfill('\0') << setprecision(8) << fixed << setiosflags(ios::right) << pi << endl; return 0; }
输出结果为
************
3.14159260
3.14159260
可以使用C语言的printf来控制浮点数的输出格式
width.precision
lf'\0'
字符(width参数可以省略)#include <cstdio>
using namespace std;
int main() {
double pi = 3.1415926;
printf("************\n");
printf("%12.8lf\n", pi); // 默认是右对齐
printf("%-12.8lf\n", pi); // 左对齐
printf("%.8lf\n", pi); // 省略width
printf("%6.8lf\n", pi); // precision >= width
return 0;
}
输出结果为
************
3.14159260
3.14159260
3.14159260
3.14159260
string类的c_str()方法返回一个指向以'\0'
结尾的字符数组的指针,该字符数组包含了与string对象相同的字符序列。
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "Hello, World!";
const char *cstr = str.c_str();
cout << cstr << endl;
return 0;
}
注意,c_str()方法返回的C风格字符串指针指向的内存是由string对象管理的,因此在string对象的生命周期内,该C风格字符串是有效的。
如果string对象被销毁或修改,那么之前返回的C风格字符串指针将不再有效。
先看以下代码
#include <iostream> #include <string> using namespace std; const char *cstr; void function() { string str = "Hello,World!"; cstr = str.c_str(); } int main() { function(); cout << cstr << endl; cout << cstr << endl; cout << cstr << endl; return 0; }
以上代码在function()函数中创建了一个局部的string对象str,并将c_str()方法返回的指针赋值给全局变量cstr。
然而,当function()函数结束时,局部变量str将被销毁,这意味着cstr现在指向的内存区域已经不再有效。
因此,当在main()函数中试图通过cout输出cstr时,程序正在访问已经被释放的内存,这是未定义的行为,可能会导致程序崩溃或输出不可预测的结果。
当然,如果你拷贝上述代码到自己电脑上运行,大概率是没有问题的。
以下是GPT4.0给出的分析和改进后的代码
虽然你的代码在运行时没有报错,但这并不意味着代码是正确的。
你的代码中存在未定义行为,这意味着程序可能会以任何方式运行,包括看似“正常”运行。
在你的例子中,尽管str已经被销毁,但其内存可能尚未被操作系统回收或重新分配给其他对象,因此你可能仍然能够看到原来的内容。
然而,这是非常不安全的,因为你无法预测何时这块内存会被重新分配。
因此,你应该避免这种未定义行为,确保你的代码总是安全、可预测的。
#include <iostream> #include <string> using namespace std; string function() { string str = "Hello, World!"; return str; } int main() { string str = function(); const char *cstr = str.c_str(); std::cout << cstr << std::endl; std::cout << cstr << std::endl; std::cout << cstr << std::endl; return 0; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。