栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

[C++](9)string类的使用:构造|赋值|遍历|容量|修改|字符串|迭代器

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

[C++](9)string类的使用:构造|赋值|遍历|容量|修改|字符串|迭代器

文章目录
  • STL简介
  • 简单了解string类
  • string类常见接口
    • 构造
    • 赋值
    • 遍历
      • 1. 下标+[]
      • 2. 迭代器(重要)
      • 3. 范围for
    • 容量操作
    • 修改操作
    • 字符串操作
    • string类非成员函数

STL简介

STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。

STL六大组件:

网上有句话说:“不懂STL,不要说你会C++”。STL是C++中的优秀作品,有了它的陪伴,许多底层的数据结构以及算法都不需要自己重新造轮子,站在前人的肩膀上,健步如飞的快速开发。

简单了解string类

string出现较早,本不是STL容器,但是它和STL容器有很多相似的操作,因此我们把它和其他STL容器放到一起学习。


C语言中,字符串是以’’结尾的一些字符的集合,虽然C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

C++中,我们有了string类,操作更加方便,快捷。


string实际上是模板实例化后typedef出来的:

string类的文档介绍string - C++ Reference (cplusplus.com)

string
typedef basic_string string;

可见其底层的类模板名叫basic_string,指定模板参数char实例化的类就是string类

类似的还有:

u16string
typedef basic_string u16string;
u32string
typedef basic_string u32string;
wstring
typedef basic_string wstring;

由于char是8位字符类型,最多只能表示256种字符,许多外文字符集所含的字符数目超过256个,char型无法表示,所以出现了其他这些字符类型。

为了适应不同的字符类型,于是就有了basic_string这个类模板


string类的一个简单使用:

int main()
{
	string s = "hello";
	s += " world!";
	cout << s << endl;
	return 0;
}
//结果:hello world!
string类常见接口 构造
构造函数说明
①string()string的默认构造,空字符串
②string(const char* s)用C-string构造string类对象
③string(const string& s)string的拷贝构造
④string(const char* s, size_t n)用C-string的前n个字符构造
⑤string(size_t n, char c)string类对象中包含n个字符c
⑥string(const string& str, size_t pos, size_t len = npos)用str中pos位置开始的len个字符构造(len默认为npos)
⑦string(InputIterator first, InputIterator last)迭代器区间初始化

最常用的:①②③

//默认构造
string s1;
//带参构造
string s2("hello world!");
string s3 = "hello world";
//拷贝构造
string s4(s2);
string s5 = s3;

不那么常用的:④⑤

string s6("ABCDEF", 3);
cout << s6 << endl; //结果:ABC
string s7(10, 'x');
cout << s7 << endl; //结果:xxxxxxxxxx

⑥

string s8(s2, 3, 5);
cout << s8 << endl; //结果:lo wo

string s9(s2, 3, 100);
cout << s9 << endl; //结果:lo world! //len的值大于要拷贝的字符串长度,则直接拷贝到字符串末尾
string s10(s2, 3);
cout << s10 << endl; //结果:lo world!

:第三个参数len默认为npos,npos是string类的一个静态成员变量,其原型为static const size_t npos = -1;-1的补码全为1,转换为无符号整型就是size_t的最大值,意味着如果不传第三个参数,就直接拷贝到str字符串的末尾。

⑦涉及迭代器,留到后面讲。

赋值
string& operator= (const string& str);
string& operator= (const char* s);
string& operator= (char c);

支持string,C-string,char类型赋值

string s1("hello world");
string s2("xxx");
s1 = s2;
s1 = "yyy";
s1 = 'z';
遍历 1. 下标+[]

此法使用最多

string类重载了[],我们可以把它当成数组来访问:

      char& operator[] (size_t pos);
const char& operator[] (size_t pos) const;

:注意这里是引用返回,而不是传值返回,目的不是减少拷贝,而是为了支持修改返回对象;第二个有const修饰的,他与第一个构成函数重载,这样做可以保证如果传入的是const修饰的string,那么返回的引用也具有const修饰。

例:

void test1()
{
    string s1("hello world!");
    for (int i = 0; i < s1.size(); i++) //size()返回该字符串字符个数
    {
        cout << s1[i] << " ";
    }
    cout << endl; //结果:h e l l o   w o r l d !
}

:string类里面还有个length成员函数,它的作用和size一样。它是为早期的string设计的,现在为了和其他STL容器统一,更多使用size


也可以通过at成员函数来访问

      char& at (size_t pos);
const char& at (size_t pos) const;

例:

void test4()
{
	string s1("hello world!");
	for (int i = 0; i < s1.size(); i++)
	{
		cout << s1.at(i) << " ";
	}
	cout << endl;
}

at与[]的区别在于,at的越界访问会抛异常,[]的越界访问通过断言来检查。

2. 迭代器(重要)

迭代器遍历虽然麻烦一点,但它是我们遍历STL容器的利器,比如list这种就用不了[],但依然可以用迭代器。

  • 现阶段可以认为迭代器是一个类似指针的东西。具体是不是指针,还要看底层实现。
  • 迭代器类型iterator是一个类所特有的,使用时要指定类域;
  • 成员函数begin()返回开始位置的迭代器,end()返回末尾的下一个位置的迭代器。

例:

void test2()
{
    string s1("hello world!");
    string::iterator it = s1.begin();
    while (it != s1.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
}

:while的判断条件it != s1.end()也可以写成it < s1.end(),因为这里的迭代器可以当成指针,而且string的存储空间是连续的。


反向迭代器

下标的反向遍历很简单,迭代器的反向遍历需要用到反向迭代器。

  • 反向迭代器的类型:reverse_iterator
  • rbegin()返回一个反向迭代器,该迭代器指向字符串最后一个字符。rend()返回的反向迭代器指向第一个字符之前的理论元素。

例:

void test5()
{
	string s1("hello world!");
	string::reverse_iterator rit = s1.rbegin();
	while (rit != s1.rend())
	{
		cout << *rit << " ";
		++rit; //此处不能写成--rit
	}
	cout << endl; //结果:! d l r o w   o l l e h
}

:正向迭代器和反向迭代器一个是正着走,一个是倒着走,但都是用的++


const迭代器

对于const修饰的对象,以上两种迭代器都不能用,因为它们既可以读也可以写。

const迭代器的类型为const_iterator或者const_reverse_iterator

const string s2("hello world!");
string::const_iterator it = s2.begin(); //begin传入const对象的指针返回的就是const迭代器
3. 范围for

string类型的对象是可迭代的,可以使用范围for。

例:

void test3()
{
	string s1("hello world!");
	for (auto ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
}
容量操作
函数说明函数原型
①size返回字符串长度size_t size() const;
②length返回字符串长度size_t length() const;
③max_size返回字符串可以达到的最大长度size_t max_size() const;
④capacity返回已分配的存储空间大小size_t capacity() const;
⑤reserve请求更改容量void reserve(size_t n = 0);
⑥resize将字符串的长度调整至nvoid resize(size_t n);
void resize(size_t n, char c);
⑦clear请掉所有数据,容量不变。void clear();
⑧empty判断是否为空bool empty() const;

①②中的①更常用一些,在上文 string类常见接口/遍历/1.下标+[] 使用过。


③max_size

例:

string s("hello world!");
cout << s.max_size();
//结果:2147483647

④capacity

我们可以通过以下程序看看编译器如何扩容:

void testCapacity()
{
	string s;
	size_t sz = s.capacity();
	cout << "capacity changed: " << sz << endl;
	for (int i = 0; i < 100; i++)
	{
		s.push_back('c'); //尾插
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << endl;
		}
	}
}
//vs2022下的结果:
//capacity changed: 15
//capacity changed: 31
//capacity changed: 47
//capacity changed: 70
//capacity changed: 105

可以看出在vs2022的环境下,除了第一次,后面都是1.5倍扩容。


⑤reserve

为了避免多次扩容造成的开销,我们可以提前使用reserve开好容量。

在VS下如果要改的容量小于当前容量,则此函数无效,即reserve能扩不能缩

例:

void testReserve()
{
	string s;
	s.reserve(1000);
	cout << s.capacity() << endl;
}
//结果:1007

⑥resize

它改变的是字符串实实在在的大小,不仅要开空间,还要对新开的空间初始化。

默认对新开的空间初始化为'',也可以自己传入要初始化的参数。

在VS下如果要改的字符串大小小于当前大小,则舍弃后面的,只保留前n个字符,capacity保持不变。

例:

void testResize()
{
	string s1("hello world!");
	s1.resize(1000); //通过监视窗口可以看到后面都是''
	string s2("hello world!");
	s2.resize(1000, 'x'); //后面都是'x'
}

⑦⑧比较常用,使用也非常简单,这里不多演示

修改操作
函数说明
①push_back在字符串后面加一个字符
②append在字符串后面加一个字符串
③operator+=在字符串后面加一个字符串
④insert插入到字符串中
⑤erase从字符串中删除字符
⑥assign为字符串指定新值,替换其当前内容。
⑦replace替换字符串的一部分
⑧swap交换两个字符串

①只能添加单个字符,②可以添加字符串,使用方式类似构造函数


③operator+=是最常用的,可读性最好

函数原型:

string& operator+= (const string& str);	 //string (1)
string& operator+= (const char* s); //c-string (2)
string& operator+= (char c); //character (3)

例:

void test()
{
	string s1("a");
	string s2("efg");
	s1 += 'b';
	s1 += "cd";
	s1 += s2;
	cout << s1 << endl;
}
//结果:abcdefg

④insert

函数原型:

string& insert (size_t pos, const string& str); //string (1)
string& insert (size_t pos, const string& str, size_t subpos, size_t sublen); //substring (2)
string& insert (size_t pos, const char* s); //c-string (3)
string& insert (size_t pos, const char* s, size_t n); //buffer (4)
string& insert (size_t pos, size_t n, char c); //fill (5)
   void insert (iterator p, size_t n, char c);
iterator insert (iterator p, char c); //single character (6)	
template  //range (7)	
   void insert (iterator p, InputIterator first, InputIterator last);

:它的函数重载太多了,但也不必刻意地去记,有需要的时候可以查文档,这里挑选常见的进行讲解。

插入单个字符可以用(5)或(6)

例:

void testInsert()
{
	string s("hello world!");
	s.insert(0, 1, 'x'); //(5)头插一个'x'
	cout << s << endl; //xhello world!
	s.insert(s.begin(), 'y'); //(6)头插'y'
	cout << s << endl; //yxhello world!

	s.insert(3, 1, 'x'); //在下标为3的位置插入一个'x'
	cout << s << endl; //yxhxello world!
	s.insert(s.begin() + 3, 'y'); //迭代器当成指针来用
	cout << s << endl; //yxhyxello world!
}

插入一个字符串可以用(1)或(3)

例:

void testInsert()
{
	string s1("hello world!");
	string s2("yes ");
	s1.insert(0, "ok "); //头插一个C-string
	cout << s1 << endl; //ok hello world!
	s1.insert(0, s2); //头插s2
	cout << s1 << endl; //yes ok hello world!
}

⑤erase

函数原型:

string& erase (size_t pos = 0, size_t len = npos); //sequence (1)
iterator erase (iterator p); //character (2)
iterator erase (iterator first, iterator last); //range (3)

例:

void testErase()
{
	string s("hello world!");
	s.erase(s.begin()); //删除一个字符
	cout << s << endl; //ello world!
	s.erase(3, 2); //删除下标3开始的2个字符
	cout << s << endl; //ellworld!
	s.erase(3); //删除下标3开始的所有字符
	cout << s << endl; //ell
}

⑥⑦用得较少,此处省略。。。


⑧swap

函数原型:

void swap (string& str);

这个swap是成员函数,另外库里还有一个swap是函数模板。

例:

void testSwap()
{
	string s1("hello world!");
	string s2("goodbye world!");
	s1.swap(s2);
	swap(s1, s2);
	cout << s1 << " " << s2 << endl;
}

那么这两个swap有什么区别呢?

  • 成员函数swap效率更高,它本质上是指针的交换。
  • 函数模板实例化的swap效率较低,它是深拷贝交换,会用到拷贝构造和赋值。
字符串操作
函数说明
①c_str返回指向这个字符串的指针(C-string)
②substr返回子字符串
③find、rfind在字符串中查找内容

①c_str

函数原型:

const char* c_str() const;

例:

void testCstr()
{
	string s("hello world!");
	const char* sp = s.c_str(); //转换为了C风格的字符串
	cout << sp << endl;
}

②substr

函数原型:

string substr (size_t pos = 0, size_t len = npos) const;

返回从pos位置开始的长度为n的字符串


③find

函数原型:

size_t find (const string& str, size_t pos = 0) const; //string (1)
size_t find (const char* s, size_t pos = 0) const; //c-string (2)
size_t find (const char* s, size_t pos, size_t n) const; //buffer (3)
size_t find (char c, size_t pos = 0) const; //character (4)
  • 返回字符串第一次匹配的首字符的位置
  • 如果找不到匹配项,则返回string::npos

比如我们给一个文件名的字符串,要取出该文件名的后缀:

void testFind()
{
	string file("string.cpp");
	size_t pos = file.find('.');
	if (pos != string::npos) //找到
	{
		string suffix = file.substr(pos, file.size() - pos);
		cout << file << "后缀:" << suffix << endl;
	}
	else //未找到
	{
		cout << "没有后缀" << endl;
	}
}
//结果:string.cpp后缀:.cpp

rfind和find的区别:find是从前往后找,rfind是从后往前找。

如果把上面的文件名换成string.c.tar.zip,用find找的就是第一个.,最后打印结果为.c.tar.zip。只想取出最后一个后缀可以用rfind

void testRfind()
{
	string file("string.c.tar.zip");
	size_t pos = file.rfind('.');
	if (pos != string::npos)
	{
		string suffix = file.substr(pos, file.size() - pos);
		cout << file << "后缀:" << suffix << endl;
	}
	else
	{
		cout << "没有后缀" << endl;
	}
}
//结果:string.c.tar.zip后缀:.zip

利用上面的字符操作函数,如何将一个网站的协议,域名,uri分别取出呢?

void test()
{
	string url1("https://blog.csdn.net/CegghnnoR/article/details/125471285");
	string& url = url1;
	string protocol; //协议
	size_t pos1 = url.find(':');
	if (pos1 != string::npos)
	{
		protocol = url.substr(0, pos1);
		cout << "protocol:" << protocol << endl;
	}
	else
	{
		cout << "非法url" << endl;
	}

	string domain; //域名
	size_t pos2 = url.find('/', pos1 + 3); //指定从pos1+3位置开始找
	if (pos1 != string::npos)
	{
		domain = url.substr(pos1 + 3, pos2 - pos1 - 3);
		cout << "domain:" << domain << endl;
	}
	else
	{
		cout << "非法url" << endl;
	}

	string uri = url.substr(pos2 + 1);
	cout << "uri:" << uri << endl;
}
//结果:
//protocol:https
//domain:blog.csdn.net
//uri:CegghnnoR/article/details/125471285
string类非成员函数

关系运算符(string)

string支持比较,可以string和string比较,也可以string和C-string比较。

void testRelational()
{
	string s1("hello world!");
	string s2("string");
	s1 < s2;
	s1 < "hello";
	"hello" < s1;

	s1 < string("hello"); //匿名对象
}

:有人吐槽,支持string和C-string比较是多此一举,C-string也可以构造成匿名对象参与比较。


operator+ (string)

+的重载,不建议多用,因为它是传值返回,增加了拷贝。


operator>> (string)、operator<< (string)

已经用了很多次了,不多说。


getline (string)

  • 获取流中的一行到str中
istream& getline (istream& is, string& str, char delim);
istream& getline (istream& is, string& str);

C语言中要通过键盘把带空格的字符串输入到字符数组中要用到gets,因为scanf是以空格和换行作为分隔符,gets只以换行为分隔符。

类似于gets,C++中可以使用getline。

void testGetline()
{
	string s;
	getline(cin, s); //输入hello world!
	cout << s << endl; //打印hello world!
}

使用getline和rfind可以轻松解决:字符串最后一个单词的长度_牛客题霸_牛客网 (nowcoder.com)

参考代码:

#include 
using namespace std;
int main()
{
    string s;
    getline(cin, s);
    size_t pos = s.rfind(' ');
    cout << s.size() - pos - 1 << endl;
    return 0;
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/996998.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号