C++课程学习笔记第八周:字符串
前言:本文主要是根据MOOC网北大课程——《程序设计与算法(三):C++面向对象程序设计》内容整理归纳而来,整理的课程大纲详见 https://www.cnblogs.com/inchbyinch/p/12398921.html
本文介绍了string类的主要操作,字符串流,以及几种字符串分割和解析成数字的方法。
1 string类
string是C++标准库的一个重要的部分,主要用于字符串处理。string类本身定义了很多方便的函数,同时C++的算法库对string也有着很好的支持,而且string还和c语言的字符串之间有着良好的接口。下面列出string对象的一些常见操作。
- 主要操作有初始化、访问、赋值、连接、比较、交换、增删查改、与C字符串交互、与流交互。
- CPP官网例子较齐全:http://www.cplusplus.com/reference/string/basic_string/
- string类是模板类:typedef basic_string< char > string
- 使用string类要包含头文件 < string >
1.1 string对象的初始化
//默认初始化
string s; //s是一个空串
//使用字符串字面值初始化
string s1="hello world"; //拷贝初始化
string s2("hello world"); //直接初始化
//使用其他字符串初始化
string s2=s1; //拷贝初始化,s1是string类对象
string s2(s1); //直接初始化,s1是string类对象
//使用单个字符初始化
string s(10, 'a'); //直接初始化,s的内容是aaaaaaaaaa
//下面是错误的初始化方法
string error1 = 'c'; // 错
string error2('u'); // 错
string error3 = 22; // 错
string error4(8); // 错
//可以将字符赋值给string对象
string s; s = 'n'; //可以
1.2 访问
//获取长度
s.length();
s.size();
//访问元素
s[i]; //不会做范围检查
s.at[i]; //会做范围检查
//获取string迭代器,通过迭代器访问
for(std::string::iterator it=str.begin(); it!=str.end(); ++it)
std::cout << *it;
std::cout << '\n';
1.3 赋值、连接、比较、交换
//赋值方式1:=
//赋值方式2:assign函数,将string对象,或char*字符串(或部分),或指定字符赋给s2
string base("The quick brown fox jumps over a lazy dog.");
string s1, s2;
s1 = "hello";
s1 = 'a';
s1 = base;
s2.assign(base);
s2.assign(base, 1, 5); //从base中下标为1的字符开始复制5个字符给s2
s2.assign(base.begin()+16, base.end()-12);
s2.assign("pangrams are cool");
s2.assign("pangrams are cool", 7); // "pangram"
s2.assign(10, '*');
//string连接方式1:+
//string连接方式2:append
string s1("good "), s2("morning! ");
s1 += s2;
s1.append(s2);
s2.append(s1, 3, s1.size()); //从s1下标3开始,复制s1.size()个字符
//如果字符串内没有足够字符,则复制到字符串最后一个字符
//比较方式1: >, >=, <, <=, ==, != 返回值为bool类型
//比较方式2:成员函数compare,返回值int:若主体s1大则为正,若小则为负,若等于则为0
string s1("hello"),s2("hello"),s3("hell");
cout << (s1 > s3) << endl; //1
cout << s1.compare(s2) << endl; //0
cout << s1.compare(s3) << endl; //1
cout << s3.compare(s1) << endl; //-1
//swap函数交换string
string s1("hello world"), s2("really");
s1.swap(s2); //成员函数
swap(s1, s2); //全局函数
1.4 增删查改
//成员函数substr获取子串
string s1("hello world"), s2;
s2 = s1.substr(4,5); //下标4开始5个字符
//查找string对象中子串,find()或rfind()
//下例中find()从前向后查找"lo"第一次出现的地方,若找到,返回"lo"开始的位置,
//即'l'所在的位置下标。否则返回string::npos(string中定义的静态常量)
string s1("hello world");
s1.find("lo"); //3
s1.find("ll",3); //4294967295 从下标3处开始查找
s1.rfind("ll"); //2 rfind()从后向前查找,用法和find()相同
//查找string对象中字符
//find_first_of(),find_last_of(),find_first_not_of(),find_last_not_of()
//下例中find_first_of从前向后查找"abcde"中任何一个字符第一次出现的地方
//而find_first_not_of是从前向后查找不在"abcde"中的字母第一次出现的地方
//如果找到则返回找到字母的位置,如果找不到,返回string::npos
string s1("hello world");
s1.find_first_of("abcde"); //1
s1.find_last_of("abcde"); //10
s1.find_first_not_of("abcde"); //0
s1.find_last_not_of("abcde"); //9
//删除erase(), 插入insert(), 替换replace()
s1.erase(5,s1.length()); //自下标5开始删除,可通过下标和迭代器均可定位
s1.insert(5,s2); // 将s2插入s1下标5的位置
s1.insert(2,s2,5,3); //将s2中下标5开始的3个字符插入s1下标2的位置
s1.replace(2,3,"haha"); //将s1中下标2开始的3个字符换成"haha"
s1.replace(2,3,"haha",1,2); //将s1中下标2开始的3个字符换成"haha"中下标1开始的2个字符
1.5 与C风格字符串交互
- C字符串可以无缝转为string对象,有三种方式;
- cout可以直接打印C字符串((const) char* 类型或数组名),无需printf;
- string对象可以通过c_str()成员函数转为C字符串,返回const char* 类型;
//C风格字符串转为string
const char *s = "Hello world!";
string str1(s);
string str2 = s;
string str3("haha");
str3 = s;
//s1.c_str()返回s1内部的const char* 类型字符串,且该字符串以‘\0’结尾。
string s1("hello world");
const char* p = s1.c_str();
cout << p << endl;
//copy(),将string复制为char* 字符串,返回复制的字符个数
char buffer[20];
string s1("Test string...");
length = s1.copy(buffer,6,5); //复制长度为6,从下标5开始(与前不同)
buffer[length]='\0';
1.6 与流交互
//string支持流读取运算符
string stringObject;
cin >> stringObject;
//string支持全局getline函数
string s;
getline(cin, s);
//getline和stringstream搭配用于split
string a, b, c, d;
string lines="adfa;asdfasd;fasdf;ccc";
stringstream line(lines); //可以直接初始化
getline(line, a, 'f');
getline(line, b, ';');
getline(line, c, ';');
getline(line, d); //默认以换行符作为delim
2 字符串流
- < sstream >库定义了三种类:istringstream、ostringstream和stringstream,分别用来进行字符串流的输入、输出和输入输出操作。
- 它内部使用string对象来代替字符数组,这样可以避免缓冲区溢出的危险。此外可以进行自动推断类型,不会因为格式不符而出错。
- stringstream作用1:通常用来做数据转换。相比c库的转换,它更加安全,自动和直接。
- stringstream作用2:与getline结合,可用于字符串切分。
- 参考1,参考2
注意:
- stringstream可以很方便地用于数据转换,注意每次转换前要调用clear();
- clear方法是清空ss的状态(比如出错等),清空内容需要使用.str("")方法(实际为替换。若需要继续用ss转换,仍然要调用clear清空状态);
- 可以利用ostringstream一次性读入需要转换成string的各个变量,利用oss.str()提取string;利用待转换的string来构造istringstream,然后一次性转成各个变量.(建议用stringstream代替。)
//示例1:利用stringstream,基本数据类型与string互转
int main(){
int a1 = 56, a2 = 0, a3=0;
double b1 = 65.123, b2 = 0.0, b3=0.0;
stringstream ss; //头文件是sstream
ss << a1 << " " << b1; //将基本类型转为string
cout << "1. " << ss.str() << endl; //1. 56 65.123
ss >> a2 >> b2; //注:ss中数据应以空格作为分隔
cout << "2. " << a2 << "---" << b2 << endl; //2. 56---65.123
ss.clear(); //每一次转换之后都必须调用clear()成员函数清空状态
//ss.str(""); //可以清空原数据
ss << b1 << " " << a1;
cout << "3. " << ss.str() << endl; //3. 56 65.12365.123 56
ss >> b3 >> a3;
cout << "4. " << ss.str() << endl; //4. 56 65.12365.123 56
cout << "5. " << a3 << "---" << b3 << endl; //5. 56---65.123
return 0;
}
//示例2:利用ostringstream和istringstream进行string与基本类型的互转
int main(){
int a=1, b=3, a1, b1;
double c=2.4, d=3.4, c1, d1;
//一次性读入需要转换成string的各个变量,并保存至string
ostringstream oss;
oss << a << " " << b << " " << c << " " << d;
cout << oss.str() << endl;
string s = oss.str();
//利用待转换的string来构造istringstream,然后一次性转成各个变量
istringstream iss(s);
iss >> a1 >> b1 >> c1 >> d1;
cout << iss.str() << endl;
cout << a1 << " " << b1 << " " << c1 << " " << d1 << endl;
return 0;
}
陷阱:
- ostringstream.str()返回一个临时的string对象,因此应按照正规写法 my_string = ostringstream.str() 将返回值及时保存,而不可用ostringstream.str().c_str()。
- c_str()返回的是一个const char* 类型的临时指针,若需要则可用自己的空间保存内容。
- 参考1,参考2
3 字符串相关知识点
3.1 一些理解
三个头文件的区别 string、cstring、string.h
- string.h 是 C 标准库提供的东西,里面有strcpy、memset等函数;
- C++为了兼容C,给.h头文件套了一个壳子,前面加个c,作为.h头文件的对应;
- 一般一个C++库老的版本带“.h”扩展名的库文件,比如string.h,在新标准后的标准库中都有一个不带“.h”扩展名的头文件(比如cstring)来对应,区别除了后者的一些改进之外,还有一点就是后者的东东都塞进了“std”名字空间中,譬如调用strlen函数,需要写成std::strlen(yourstr)才行;
- 如果你用的是C++,那么请用cstring,如果你用的是C请用string.h;
- string头文件是C++定义的std::string所使用的文件,是string类的头文件,属于STL范畴
cstring/string.h头文件中常用的函数
- strlen, strcpy, strcat, strcmp
- memcpy, memset, strtok
STL中排序函数 sort 机制:
- 参数cmp为比较函数,传入两个参数,若为true,则符合要求;
- 因此,在比较函数中,若 return a < b,则为升序,若 return a > b,则为降序;
3.2 字符串分割的方法
- 方法1:利用字符串流;(需要以空格作为分割符)
- 方法2:利用字符串流和全局getline函数;(示例见1.6)
- 方法3:利用cstring中std::strtok()函数。
#include <iostream>
#include <cstring>
#include <string>
using namespace std;
int main(){
//目的是将一个string对象按照分隔符分割成几个子串对象
//通过C风格的strtok函数进行分割
//char * strtok(char* str, const char* delimiters);
//由于strtok函数需要传入C风格的字符串,且会破坏字符串结构,故复制原string对象内容
string s = "hello, big - world.";
string str1, str2, str3;
char a[100] = {0};
std::strcpy(a, s.c_str()); //c_str()函数返回const char*类型
cout << a << endl; //输出hello, big - world.
str1 = std::strtok(a, " ,-.");
str2 = std::strtok(NULL, " ,-.");
str3 = std::strtok(NULL, " ,-.");
cout << a << endl; //输出hello
cout << str1 << "-" << str2 << "-" << str3; //输出hello-big-world
return 0;
}
3.3 字符串解析成数字的方法
- 方法1:利用字符串流,find()函数定位;
- 方法2:利用正则表达式,或者C中scanf函数;
- 方法3:利用cstdlib中std::strtod()函数。
#include <iostream>
#include <cstring>
#include <cstdlib>
using namespace std;
class Complex{
private:
double r, i;
public:
Complex(){};
Complex(const char* p){
char a[20];
char* pEnd;
std::strcpy(a, p);
r = std::strtod(a, &pEnd);
i = std::strtod(pEnd+1, NULL);
}
void Print() {
cout << r << "+" << i << "i" << endl;
}
};
int main(){
Complex a;
a = "3+4i"; a.Print();
a = "5+6i"; a.Print();
return 0;
}