跳到主要内容

string

string s = "Hello";

std提供了string类型,弥补了简单字符串字面值的不足,还提供了很多有用的字符串操作.并且相较于其他 STL 容器,string 的常数可以算是非常优秀的,基本与字符数组不相上下。

  • string 可以动态分配空间: 和许多 STL 容器相同,string 能动态分配空间,这使得我们可以直接使用 std::cin 来输入,但其速度则同样较慢。这一点也同样让我们不必为内存而烦恼。
  • string 重载了加法运算符和比较运算符string 的加法运算符可以直接拼接两个字符串或一个字符串和一个字符。
  • std::vector 类似,string 重载了比较运算符,同样是按字典序比较的,所以我们可以直接调用 std::sort 对若干字符串进行排序。
basic_string

std::string 是在标准库 <string>(注意不是 C 语言中的 <string.h> 库)中提供的一个类,本质上是 std::basic_string<char> 的别称。

basic_string与vector类似,不同之处在于其元素为字符类型,并提供了一系列与字符串相关的操作。 C风格字符串(以\0结尾的字符数组)太过复杂,难于掌握,不太适合大程序的开发,所以C++STL中定义了一种string类,在头文件<string>中。

string和wstring分别是它的两个实例:

  1. basic_string<char>
  2. basic_string<wchar_t>

string 本质上是一个动态的char数组。char*是一个指针,string是一个类, string封装了char*,管理这个字符串,是一个char*型的容器。string管理char*所分配的内存,每一次string的复制/赋值,取值都由string类负责维护,不用担心复制越界和取值越界等。

转为char数组

在 C 语言里,也有很多字符串的函数,但是它们的参数都是 char 指针类型的,为了方便使用,string 有两个成员函数能够将自己转换为 char 指针——data()/c_str()(它们几乎是一样的,但最好使用 c_str(),因为 c_str() 保证末尾有空字符,而 data() 则不保证),如:

printf("%s", s);          // 编译错误
printf("%s", s.data()); // 编译通过,但是是 undefined behavior
printf("%s", s.c_str()); // 一定能够正确输出

构造函数

string();// 默认构造函数,创建一个空的字符串
string(const string& str);// 拷贝构造函数,使用一个string对象初始化另一个string对象
string(const char* s);// 含参构造函数,使用C风格字符串初始化
string(int n, char c);// p含参构造函数,使用n个字符c初始化

成员函数

赋值

通过运算器赋值
string& operator=(const char* s);// C风格字符串赋值给当前的字符串
string& operator=(const string& s);// 把字符串s赋给当前的字符串
string& operator=(const char c);//字符赋值给当前的字符串
assign()成员函数赋值
string& assign(const char* s); // C风格字符串赋值给当前的字符串
string& assign(const char* s, int n); // 把C风格字符串s的前n个字符赋给当前的字符串
string& assign(const string& s); // 把字符串s赋给当前字符串
string& assign(int n, char c); // 把n个字符c赋给当前的字符串
string& assign(const string& s, int start, int n); // 将字符串s中从start开始的n个字符赋值给当前字符串

获取字符

char& operator[](int n); // 通过[]下标方式获取字符

使用下标操作符获取字符时,如果下标越界,程序将会强制终止。

char& at(int n); // 通过at方法获取字符

使用at方法获取字符时,如果下标越界,at方法内部会抛出异常(exception),可以使用try-catch捕获并处理该异常。

//标准异常头文件
#include <stdexception>
int main()
{
string s = "hello world";
try
{
//s[100]不会抛出异常,程序会直接挂掉
s.at(100);
}
catch (out_of_range& e)
//如果不熟悉异常类型,可以使用多态特性, catch(exception& e)。
{
cout << e.what() << endl;
//打印异常信息
}
return 0;
}

复合

运算器函数复合操作
string& operator+=(const string& str); // 将字符串str追加到当前字符串末尾
string& operator+=(const char* str); // 将C风格字符数组追加到当前字符串末尾
string& operator+=(const char c); // 将字符c追加到当前字符串末尾
/* 上述操作重载了复合操作符+= */

//append成员函数
string& append(const char* s); // 把C风格字符数组s连接到当前字符串结尾
string& append(const char* s, int n); // 把C风格字符数组s的前n个字符连接到当前字符串结尾
string& append(const string &s); // 将字符串s追加到当前字符串末尾
string& append(const string&s, int pos, int n); // 把字符串s中从pos开始的n个字符连接到当前字符串结尾
string& append(int n, char c); // 在当前字符串结尾添加n个字符c

查找和替换

int find(const string& str, int pos = 0) const; // 查找str在当前字符串中第一次出现的位置,从pos开始查找,pos默认为0
int find(const char* s, int n = 0) const; // 查找C风格字符串s在当前字符串中第一次出现的位置,从pos开始查找,pos默认为0
int find(const char* s, int pos, int n) const;// 从pos位置查找s的前n个字符在当前字符串中第一次出现的位置
int find(const char c, int pos = 0) const; // 查找字符c第一次出现的位置,从pos开始查找,pos默认为0

当查找失败时,find方法会返回-1,-1已经被封装为string的静态成员常量string::npos

static const size_t nops = -1;
int rfind(const string& str, int pos = npos) const; // 从pos开始向左查找最后一次出现的位置,pos默认为npos
int rfind(const char* s, int pos = npos) const; // 查找s最后一次出现的位置,从pos开始向左查找,pos默认为npos
int rfind(const char* s, int pos, int n) const; // 从pos开始向左查找s的前n个字符最后一次出现的位置
int rfind(const char c, int pos = npos) const; // 查找字符c最后一次出现的位置

find方法通常查找字串第一次出现的位置,而rfind方法通常查找字串最后一次出现的位置。

rfind(str, pos)的实际的开始位置是pos + str.size(),即从该位置开始(不包括该位置字符)向前寻找匹配项,如果有则返回字符串位置,如果没有返回string::npos。

-1其实是size_t类的最大值(学过补码的同学应该不难理解),所以string::npos还可以表示“直到字符串结束”,这样的话rfind中pos的默认参数是不是就不难理解啦?

替换
string& replace(int pos, int n, const string& str); // 替换从pos开始n个字符为字符串s
string& replace(int pos, int n, const char* s);// 替换从pos开始的n个字符为字符串s

比较

int compare(const string& s) const; // 与字符串s比较
int compare(const char* s) const; // 与C风格字符数组比较

compare函数依据字典序比较,在当前字符串比给定字符串小时返回-1,在当前字符串比给定字符串大时返回1,相等时返回0。

比较操作符

bool operator<(const string& str) const;
bool operator<(const char* str) const;
bool operator<=(const string& str) const;
bool operator<=(const char* str) const;
bool operator==(const string& str) const;
bool operator==(const char* str) const;
bool operator>(const string& str) const;
bool operator>(const char* str) const;
bool operator>=(const string& str) const;
bool operator>=(const char* str) const;
bool operator!=(const string& str) const;
bool operator!=(const char* str) const;

string类重载了所有的比较操作符,其含义与比较操作符本身的含义相同。

子串

string substr(int pos = 0, int n = npos) const;// 返回由pos开始的n个字符组成的字符串

插入和删除

string& insert(int pos, const char* s); // 在pos位置插入C风格字符数组
string& insert(int pos, const string& str); // 在pos位置插入字符串str
string& insert(int pos, int n, char c); // 在pos位置插入n个字符c

返回值是插入后的字符串结果,erase同理。其实就是指向自身的一个引用。

string& erase(int pos, int n = npos); // 删除从pos位置开始的n个字符

默认一直删除到末尾。

删除固定字符:算法库配合

s.erase(remove(s.begin(), s.end(), '-'), s.end()); // 删除erase
  1. remove(s.begin(), s.end(), '-'):这个函数在 s 的范围内(从 s.begin() 到 s.end())查找所有的连字符('-'),并将它们移动到末尾,但不会真正删除它们,而是返回一个指向第一个需要删除的元素的迭代器。
  2. s.erase(remove(s.begin(), s.end(), '-'), s.end()):这一行将调用 erase 函数来删除 remove 函数返回的迭代器指向的所有连字符之后的元素,实现了真正的删除操作。

string和const char * 的转换

string to const char *
string str = "demo";
const char* cstr = str.c_str();
const char* to string
const char* cstr = "demo";
string str(cstr); // 本质上其实是一个有参构造

与string相关的全局函数

大小写转换
#include <cctype>
// 在iostream中已经包含了这个头文件,如果没有包含iostream头文件,则需手动包含cctype

int tolower(int c); // 如果字符c是大写字母,则返回其小写形式,否则返回本身
int toupper(int c); // 如果字符c是小写字母,则返回其大写形式,否则返回本身

/**
* C语言中字符就是整数,这两个函数是从C库沿袭过来的,保留了C的风格
*/

如果想要对整个字符串进行大小写转化,则需要使用一个for循环,或者配合和algorithm库来实现。例如:

#include <string>
#include <cctype>
#include <algorithm>

string str = "Hello, World!";
transform(str.begin(), str.end(), str.begin(), toupper); //字符串转大写
transform(str.begin(), str.end(), str.begin(), tolower); //字符串转小写

连接

string compose(const string& name ,const string& domain){
return name + "@" + domain;
};

auto addr = compose("wangenius","qq.com")

stirng s = "ok";
s += '\n';//追加换行

下标操作

string name = "wangenius";

string s = name.substr(3,6); // "genius"
name.replace(4,9,"zheng"); // "wangzheng"
name[0] = toupper(name[0]); // "Wangenius"

如果一个c风格字符串(以0结尾的char数组),string支持对其包含的字符进行只读访问:

void print(const string& s){
printf("for people who like printf: %s \n",s.c_str());//s.c_str()返回指向s字符的指针
cout << "for people who like streams:" << s << '\n';
};

字符串字面值的类型是const char *类型, 为了得到std::string类型的字面需要使用后缀s

auto s = "wangenius"s; // std::string类型
auto p = "wangenius"; //c风格字符串 const char * 类型

string类的实现

短字符串优化技术: 短字符串(大约14个字符)直接保存在string对象内部, 长字符串保存在动态存储区.

string的实际性能严重依赖运行时环境. 特别是在多线程实现中, 内存分配操作的代价相对较高. 当程序使用大量长度不一样的字符串时, 内存碎片问题会很严重. 所以采用短字符串优化技术.

为了处理多字符集, std定义了一个通用的字符串模板basic_string, string实际上是此模板用字符类型char实例化的一个别名;

string视图

字符序列最常见的用途是传递给某个函数让它读取. 这可以通过字符串的值,引用或者c风格字符串方式传递string参数来实现. 都有额外的复杂性. std提供了string_view, 是一个(指针,长度)对, 表示一个字符序列.

string_view : {begin(),size()}

我们通过string_view可以实现对一个连续字符序列的访问. 字符的存储可以是很多方式之一,包括string和char*. string_view类似指针或者引用,因为它并不拥有它所指向的字符. 这一点上,它很想STL迭代器对.

一个简单的将字符串连接的代码块
string cat(string_view sv1, string_view sv2){
string res(sv1.length() + sv2.length());
char * p = &res[0];
for (char c : sv1) //一种拷贝方法
*p++ = c;
copy(sv2.begin(), sv2.end(),p); //另一种拷贝方法
return res;
};
//调用
string name = "wangenius";

auto s1 = cat(name,".com");
auto s2 = cat(name,name);
auto s3 = cat("wangenius",".com"sv); //const char* && string_view
auto s4 = cat({&name[0],2},".com"sv); //wa.com

这个cat()和接受const string& 实参的compose()相比:

  1. 它可以用以不同方式管理的字符序列
  2. 对于C风格字符串实参,不会创建临时string实参
  3. 我们可以简单的传递子串

后缀sv表示字符串视图, 必须:

using namespace std::literals::string::string_view_literals;

empty()

s.empty(); // s为空字符串返回1, 否则返回0

size()

s.size(); // s的大小 == 5;

substr()

s.substr(off,count); // off是开始的位置,count是子字符串个数

Regex

#include <regex>;
std::regex pat {R"(\w{2}\s*\d{5}(-\d{4})?)"}; //美国邮政编码 xxddddd-dddd及其变形
  1. regex_match() : 将regex和一个字符串进行匹配
  2. regex_search() : 在一个任意长的数据流中搜索与正则表达式匹配的字符串
  3. regex_replace() : 在一个任意长的数据流中搜索与正则表达式匹配的字符串并将其替换
  4. regex_iterator : 遍历匹配结果和子匹配
  5. regex_token_iterator : 遍历未匹配结果部分

举例:搜索的使用模式的最简单的方式就是在流中搜索它:

int lineno = 0;
for(string line; getline(cin,line);){
++lineno;
smatch matches;
if(regex_search(line,matches,pat)){
cout << lineno << ":" << matches[0] << endl;
}
}
Loading Comments...