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;
}
posted @ 2020-03-02 23:35  天地辽阔  阅读(373)  评论(0编辑  收藏  举报