【C++】——string类的使用

如意.759 2024-09-12 13:05:02 阅读 55

目录

一.为什么学习string类?

1.1 C语言的字符

二. 标准库中的string类

2.1 string类(了解)

2.2 string类成员函数

● string类对象的常见构造

● string类析构函数

● 赋值重载

2.3 string的迭代器

<1>正向迭代器 Iterator

<2> 反向迭代器 reverse_iterator   

<3> 迭代器访问const类型容器

2.4 string类的容量操作

2.5 string类的访问修改

<1>元素访问

<2>元素修改

<3>子字符串

2.6 string类的查找

2.7 string类的非成员函数

● 运算符重载+

● 比较运算符重载

● getline(将线从流转换为字符串)


一.为什么学习string类?

1.1 C语言的字符串

C语言中,字符串是以 '\0' 结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合面向对象编程(OOP)的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、 快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。

string类相比C语言的字符串更安全、更方便、更高效的使用,是面向对象编程的思想体现

二. 标准库中的string类

2.1 string类(了解)

string类的文档介绍

在使用string类时,必须包含头文件#include<string>以及using namespace std;

2.2 string类成员函数

● string类对象的常见构造

<1>string(); 无参构造,空的 string 只有  "\0"

<2>string (const string& str); 拷贝构造函数

<3>string (const string& str, size_t pos, size_t len = npos);

(其他负数也可以起到一样的效果)

拷贝构造,在指定下标 pos 位置拷贝 len 个字符 默认值为 npos 类型为无符号整数类型,因此 npos 为无符号整数最大值,库规定 len 大于字符串长度,则拷贝整个 str 到结束

<4>string (const char* s); 将字符串拷贝给 string 类

<5>string (const char* s, size_t n); 将字符串的前 n 个拷贝给 string 类

<6>string (size_t n, char c); 将 n 个字符 c 拷贝给 string 类

<code>int main()

{

string s1;//无参构造 只有\0

string s11{};//同上 string s1(); 编译器可能认为声明

string s2("hello world");//将括号内字符串拷贝给s2

string s3(s2, 0, 2);//将s2从第0个位置拷贝2个元素给s3

string s4(s2);//拷贝构造 将s2类拷贝给s4

string s5(s2, 5);//拷贝构造 将s2类的前5个字符拷贝给s5

string s6(8, 'a');//拷贝n个字符a给s6

cout << s1 << endl;

cout << s11 << endl;

cout << s2 << endl;

cout << s3 << endl;

cout << s4 << endl;

cout << s5 << endl;

cout << s6 << endl;

return 0;

}

● string类析构函数

字符串生命周期结束,会自动调用析构函数。

● 赋值重载

2.3 string的迭代器

<1>正向迭代器 Iterator

提供了一种通用的(所用)访问容器的方式

<code>string s1("hello");

// it 作用类似指针

string::iterator it = s1.begin();

while (it != s1.end())//end 返回最后一个字符下一个位置 左闭右开

{

*it += 2;//可以修改内容

cout << *it << ' ';

++it;

}

cout << endl;

● begin  返回 指向 string 第一个字符

● end 返回 指向 string 最后一个有效字符下一个位置 \0

<2> 反向迭代器 reverse_iterator   

<code>string s1("hello");

// it 作用类似指针

string::reverse_iterator it = s1.rbegin();//反向指向string的最后一个有效字符 视为开始端

auto end = s1.rend();//指向 string的第一个有效字符前一个位置 视为结尾端

while (it != s1.rend())

{

cout << *it << ' ';

++it;

}

cout << endl;

● rbegin

● end

<3> 迭代器访问const类型容器

普通的迭代器是可读可写的,const迭代器访问const修饰的容器只能读不能写(指向的内容)

Tip:const迭代迭代器不是指const 修饰迭代器,因为迭代器本身可以修改,而它指向的内容不能修改!

<code>string s1("hello");

// it 作用类似指针

string::const_iterator it = s1.cbegin(); //也可以写成 begin 相当于权限缩小

while (it != s1.end())

{

/**it += 2;*/

cout << *it << ' ';

++it;

}

cout << endl;

三种访问方式:

<code>string s1("hello world");

cout << s1 << endl;

for (int i = 0; i < s1.size(); i++)

{

cout << s1[i] << ' ';

}

cout << endl;

string::iterator it = s1.begin();

auto it2 = s1.begin();

while (it2 != s1.end())

{

cout << *it2 << ' ';

it2++;

}

cout << endl;

//自动推导类型 自动赋值 自动迭代结束 底层迭代器

//本质拷贝赋值 不能修改原数据 将*it值 赋值给 c

for (auto c : s1)

{

cout << c << ' ';

}

cout << endl;

//引用 可以修改

for (auto& c : s1)

{

cout << c << ' ';

}

cout << endl;

2.4 string类的容量操作

std::string 里内部结构可能优化了,比如 char[] _buff 在这种情况下,短字符串可能直接存储在 std::string 对象的内存空间内,而不是在堆上分配。但是,当字符串增长超出了这个内部空间时,就会触发堆上的内存分配和扩容。

连续尾插测试扩容机制:

<code>int main()

{

string s;

size_t sz = s.capacity();

cout << "capacity changed:" << sz << '\n';

cout << "making a grow:\n";

for (int i = 0; i < 100; i++)

{

s.push_back('c');

if (sz != s.capacity())

{

sz = s.capacity();

cout << "capacity changed:" << sz << '\n';

}

}

return 0;

}

可以看到 由于 _buff 的存在 初始容量为15个字节,再继续尾插之后该编译器出现了 1.5倍的扩容机制以减少将来可能的内存重新分配次数,提高性能。

容量只显示有效字符的个数,内存会比容量多一个空间(存储‘ \0 ’)

● 总结:

<1> size() 与 length() 方法底层实现原理完全相同,引入size() 的原因是为了与其他容器的接

口保持一致, 一般情况下基本都是用size() 

<2> capacity() 返回有效数据空间大小,不包含 \0 实际需要多开一个空间

<3> clear() 将string中有效字符清空,不改变底层空间大小。

<4>  resize(size_t n) 与 resize(size_t n, char c) 都是将字符串中有效字符个数改变到n个 ,不

同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char

c) 用字符c来填充多出的元素空间 。

注意: resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。

<5>reserve(size_t res_arg=0) 为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。

vs下,有效元素个数小于预留空间大小时,不缩容。

g++下,有效元素个数小于预留空间大小时,缩容。

<code>int main()

{

string s2("hello xxxxxxxxxxxxxxxxxx");

cout << s2.size() << endl;

cout << s2.capacity() << endl << endl;

//给一个小于 size的值

s2.reserve(20); //结果不变

cout << s2.size() << endl;

cout << s2.capacity() << endl << endl;

//给一个 size与 capacit 中间值

s2.reserve(28); //结果不变

cout << s2.size() << endl;

cout << s2.capacity() << endl << endl;

//给一个 大于 capacit 值

s2.reserve(40); //扩容

cout << s2.size() << endl;

cout << s2.capacity() << endl << endl;

s2.clear();//清楚数据 不清容量

cout << s2.size() << endl;

cout << s2.capacity() << endl << endl;

return 0;

}

2.5 string类的访问修改

<1>元素访问

● 运算符重载[ ]  返回pos位置字符的引用

模拟实现:

<code>char& operator[](size_t pos)

{

assert( pos>=0 &&pos < _size);

return _str[pos];

}

const char& operator[](size_t pos)const

{

assert(pos >= 0 && pos < _size);

return _str[pos];

}

● at 获取字符串中的字符

at 函数自动检查 pos 是否是字符串中字符的有效位置(即 pos 是否小于字符串长度),如果不是,则抛出 out_of_range 异常。

<2>元素修改

● push_back   尾部插入一个字符c

● append   在字符串后追加字符串str

● 赋值重载+=   字符串后追加字符串str 

<code>int main()

{

string s2("world");

string s("hello worold");

s.push_back(' ');//单个字符

s.push_back('x');

s.append(" ");

s.append(s2);//字符串

cout << s << endl;

s += ' ';

s += "ccccccc";

cout << s << endl;

return 0;

}

注意:

1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c' 三种的实现方式差不多,一般情况下string类的 += 操作用的比较多,+= 操作不仅可以连接单个字符,还可以连接字符串。

2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。

● insert(插入) 在pos或p位置之前插入字符串

● erase(删除)pos位置删除字符

 ● replace(替换)

<code>int main()

{

string s("hello worold");

//模拟头插 需要移动数据 效率相对低

s.insert(0, "hello xc ");

cout << s << endl;

//模拟头删 需要移动数据

s.erase(0, 1);

cout << s << endl;

s.erase(s.begin());

cout << s << endl;

//模拟尾删

s.erase(--s.end());

cout << s << endl;

s.erase(s.size() - 1, 1);

cout << s << endl;

//将空格替换

string ss("hello world");

cout << ss << endl;

ss.replace(5, 1, "%%");

cout << ss << endl;

cout << endl;

return 0;

}

<3>子字符

● c_str(兼容C)返回一个指向字符数组的指针 数组内容即 string 对象

<code>// string 不能直接与 文件操作对接

string file;

cin >> file;

FILE* fout = fopen(file.c_str(),"r");

char ch = fgetc(fout);

while (ch != EOF)

{

cout << ch;

ch= fgetc(fout);

}

fclose(fout);

● data(类似于c_str)

● substr(子串)在str中从pos位置开始,截取n个字符,然后将其返回

2.6 string类的查找

● find(正向) 在字符串里查找字符数据 pos位置开始 n个数据

<code>//将字符串的空格替换成百分号

//法一 移动数据频繁 效率低

string sss("hello world hello bit");

cout << sss << endl;

size_t pos = sss.find(' ');

while (pos != string::npos)

{

sss.replace(pos, 1, "%%");

pos = sss.find(' ',pos+2);

}

cout << sss << endl<<endl;

//法二 构造新串 空间换时间

string tmp;

tmp.reserve(sss.size());

for (auto ch : sss)

{

if (ch == ' ')

tmp += "%%";

else

tmp += ch;

}

cout << tmp << endl;

sss.swap(tmp);// 交换 字符指针

cout << sss << endl << endl;

● rfind(倒着找) 与find 参数一致意义 倒着查找

<code>//找出文件后缀

string s2("test.cpp.zip");

size_t pos2 = s2.rfind('.');

string suffix2 = s2.substr(pos2);

cout << suffix2 << endl;

● find_first_of  在字符串中搜索与其参数中指定的任何字符匹配的第一个字符。

<code>int main()

{

string s3("hello world hello xc");

cout << s3 << endl;

size_t pos3 = s3.find_first_of("abcd");// find_first_of 给定一个需要查找的字符串,一个一个查找,返回索引

while (pos3 != string::npos)

{

s3[pos3] = '*';

pos3 = s3.find_first_of("abcd", pos3 + 1);

}

cout << s3 << endl;

return 0;

}

● find_last_of(倒着找)倒序查找

<code>

void SplitFilename(const std::string& str)

{

std::cout << "Splitting: " << str << '\n';

std::size_t found = str.find_last_of("/\\");

std::cout << " path: " << str.substr(0, found) << '\n';

std::cout << " file: " << str.substr(found + 1) << '\n';

}

//文件路径分离

string str1("/usr/bin/man");

string str2("c:\\windows\\winhelp.exe");

SplitFilename(str1);

SplitFilename(str2);

● find_first_not_of    查找字符串中与参数中指定的任何字符都不匹配的第一个字符

● find_last_not_of    倒叙查找字符串中与参数中指定的任何字符都不匹配的第一个字符

2.7 string类的非成员函数

● 运算符重载+

返回一个新构造的字符串对象,其值是lhs中字符的连接,后跟rhs中的字符。

<code>int main()

{

string s1("hello");

string s2 = s1 + " wrold";

string s3 = s1 + s2;

cout << s1 << endl;

cout << s2 << endl;

cout << s3 << endl;

return 0;

}

● 比较运算符重载

字符串对象lhs和rhs之间执行适当的比较操作

● getline(将线从流转换为字符串)

从is中提取字符并将其存储到str中,直到找到分隔符delim

找最后一个单词长度:

<code>#include <iostream>

using namespace std;

int main() {

string str;

//cin >> str;// cin 和 scanf 将空格和换行默认成分割

getline(cin,str);// 无定界默认遇到换行才停止输入

size_t pos = str.rfind(' ');

cout << str.size() - (pos + 1) << endl;

}

可以自定义分隔符

getline(cin,str,"*");//流提取,直到遇到*才停止



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。