当前位置: 首页 > news >正文

【C++】--- string的使用

string的使用

    • 1.构造函数的使用
    • 2. string类对象的访问及遍历操作
      • 2.1operator[]实现遍历
      • 2.2 普通迭代器正序遍历
      • 2.3 auto/范围for
        • 2.3.1 auto
        • 2.3.2 范围for
      • 2.4 普通迭代器逆序遍历
      • 2.5 const迭代器
    • 3.string类对象的容量操作
      • 3.1 size和length
      • 3.2 max_siz
      • 3.3 capacity
      • 3.4 clear
      • 3.5 resize
    • 4.string类对象的修改操作
      • 4.1 push_back
      • 4.2 append
      • 4.3 operator+=
      • 4.4 不同平台下string的扩容机制
      • 4.5 find和replace 替换指定字符
    • 5. 剩下的部分接口
      • 5.1 c_str
      • 5.2 refind 和 substr 取文件名的后缀

在这里插入图片描述
string是basic_string的模板char类型实现,相比于其他类型的模板实现,string是最常用的。

1.构造函数的使用

#include<iostream>
using namespace std;
int main()
{
	string s1; //string(); 无参构造,构造空字符串
	string s2("hello world"); //string(const char* s)  用C-string来构造string类对象
	string s3  = s2;//string(const string&s) 拷贝构造函数
	string s4(5, 'c');//string(size_t n, char c) string类对象中包含n个字符c

	cout << s1<< endl;
	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;
	return 0;
}

在这里插入图片描述

2. string类对象的访问及遍历操作

2.1operator[]实现遍历

int main()
{
	 string s1("hello world");
	for (int i = 0; i < s1.size(); i++)//size()返回容器中元素的个数
	{
		cout << s1[i] << " ";
	}
	return 0;
}

在这里插入图片描述
s1[i]这里是一个函数重载,原型是s1.operator[].(i)。operator[]的实现大致如下:

char& operator[] (size_t pos)
	{
		assert(pos < _size);
		return _str[pos];
	}

char&引用返回允许我们修改返回字符。
在这里插入图片描述
库函数中还实现了const版本的operator[]函数,const的string对象使用operator[]就会调用const版本的重载,当然这个版本不允许修改返回字符。

2.2 普通迭代器正序遍历

int main()
{
	string s1("hello world");
	string::iterator it1 = s1.begin();
	while (it1 != s1.end())//最后一个数据的下一个位置  '\0'
	{
		cout << *it1 << " ";
		it1++;
	}
	return 0;
}

在这里插入图片描述

  1. 迭代器的使用很像指针,有些容器的迭代器底层实现是指针,有些不是。
  2. 迭代器可以实现对字符串对象的读写。
  3. 迭代器是一种通用的方式,operator[]只适用于元素地址连续的容器。
  4. 迭代器是通过运算符重载的方式来实现的。

2.3 auto/范围for

2.3.1 auto

auto是C++11给出一个"语法糖",会自动推导类型。

int main()
{
	int i = 0;
	auto z = i;
	auto x = 1.1;
	auto p = &i;
	return 0;
}
int main()
{
	int& r1 = i;
	auto r2 = r1; //int 
	auto& r3 = r1; //int& r1的引用
}

auto无法推导出引用,r1是i的引用,但是r2并不是r1的引用,这里的quto r2 = r1等价于int r2 = r1,可以使用auto&来解决问题。
在这里插入图片描述
另外 auto 需要被初始化,auto r4;这样无法推导类型,是错误的。下面将体现auto的使用价值。

int main()
{
	//auto的价值1:方便   
	auto it1 = list1.begin();
	std::map<std::string, std::string> dict;
	//std::map<std::string, std::string>::iterator;
	auto dit = dict.begin();
}

这样就可以替代写起来很长的类型,可以简化代码。

2.3.2 范围for

C++范围for也是C++11的一个语法糖,用来遍历容器,底层是迭代器 当底层支持迭代器的时候,才可以使用范围for
如何使用范围for遍历一个string对象呢?

int main()
{
	string s1("hello world");
	//自动取容器的数据赋值给左边的值,自动判定结束,自动++;
	for (char ch : s1)
	{
		cout << ch << " ";
	}
	return 0;
}

在这里插入图片描述
一般使用范围for的时候一般搭配auto使用,上面的例子可以写为for(auto ch: s1)

string s1("hello world");
for (char ch : s1)
{
	cout << ch << " ";
}
cout << endl;
for (char ch : s1)
{
	ch++;
}
for (char ch : s1)
{
	cout << ch << " ";
}
cout << endl;

由于范围for是把容器拷贝给auto对象,所以范围for不修改原容器。
在这里插入图片描述
可以使用引用来修改容器。

for (char& ch : s1)
{
	ch++; 
}

在这里插入图片描述
范围for搭配引用的使用范围

  1. 需要修改容器
  2. 类型占内存很大
  3. 类型很大同时又不想修改的时候可以考虑 const auto&

有个例外,数组也支持范围for,可以认为是编译器的特殊处理

int main()
{
	int a[] = { 1,2,3,4,5,6 };
	for (auto ae : a)
	{
		cout << ae << " ";
	}
 	return 0;
}

从C++20开始支持auto做参数,C++11开始就支持auto做返回值了。

auto Func(auto x)
{ ..}

但是auto做返回值,会导致程序不够清晰明了。

auto func3()
{
	auto y = func4();
	return  y;
}
auto func2()
{
	auto x = func3();
	return x;
}
int main()
{
	auto ret1 = func();
}

ret1的类型是什么呢?auto做返回值要谨慎使用

2.4 普通迭代器逆序遍历

如何倒叙遍历一个字符串对象呢?迭代器分为正向迭代器iterator反向迭代器reverse_iterator,反向迭代器可以实现倒叙遍历。

int main()
{
	string s1("hello world");
	auto rit = s1.rbegin();
	while (rit != s1.rend())
	{
		cout << *rit << " ";
		++rit;  //++ 不是 -- 
	}
	return 0;
}

2.5 const迭代器

const对象无法使用普通的迭代器,const迭代器也无法修改原容器
与普通迭代器相同,const迭代器也分为正向迭代器const_iterator和反向迭代器const_reverse_iterator

int main()
{
	string s1("hello world!");
	const string s2(s1);
	//const对象无法使用普通的迭代器
	//auto it = s2.begin();
	string::const_iterator it = s2.begin();
	while (it != s2.end())
	{
		cout << *it << " ";
		++it;  
		//*it1 += 1;  无法修改
	}
	cout << endl;
	string::const_reverse_iterator it1 = s2.rbegin();
	while (it1 != s2.rend())
	{
		cout << *it1 << " ";
		++it1;
		//*it1 += 1;  //const迭代器无法修改
	}
	return 0;
}

在这里插入图片描述

3.string类对象的容量操作

3.1 size和length

获取string对象中的有效字符的个数

int main()
{
	string s1("hello world");
	cout << s1.size() << endl;
	cout << s1.length() << endl
	return 0;
}

在这里插入图片描述
size和length的功能相同,为什么要都实现出来呢,着不得不提出发展历史的原理,string的涉及比STL要早,string起初设置的是length,但是STL的都命名为size,string类也就补充了一个size,size的命名方式比lengt通用,所以推荐使用size。

3.2 max_siz

表示容器可最大申请的个数,理想化的,没有意义,在不同的环境下不同

int main()
{
	string s1("hello world");
	cout << s1.max_size() << endl;
	return 0;
}

在这里插入图片描述
在X86环境下,可以达到21亿多,但是实际是达不到的。

3.3 capacity

可以存储的有效字符的个数底层跟顺序表的设计类似,可实现自动扩容

int main()
{
	string s1("hello world");
	cout << s1.capacity() << endl;
	return 0;
}

在这里插入图片描述
这里实际存储的容量是16,有效字符的后一个位置存\0,和C语言保持一致。

3.4 clear

清除数据,一般不清除空间数据,只修改size,然后首位置改为\0,具体的实现更为复杂。

int main()
{
	string s1("hello world1111");
	s1.clear();
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	return 0;
}

在这里插入图片描述
这里还有三个接口没有讲

3.5 resize

resize会有三种情况

  1. resize < size
  2. size < resize < capacity
  3. resize > capacity

第一种是一个删除的行为,第二三种就是插入的行为,第三种还涉及到扩容。

int main() //resize
{
	string s1("hello world");
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;

	s1.resize(10);  //删除数据
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;

	s1.resize(14);  //插入
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl; //调试后发现插入的是'\0',最后面一个被认为是终结符。

	s1.resize(20,'x'); //扩容并插入
	cout << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;

	return 0;
}

在这里插入图片描述

在这里插入图片描述
如果没有设置插入的字符,默认就是\0,并且只有最后一个\0会被认定为是终结符。

4.string类对象的修改操作

这一部分也是string设计比较乱的地方

4.1 push_back

在字符串后尾插字符c

int main()
{
	string s1("hello");
	//尾插字符
	s1.push_back(',');
	s1.push_back('w');
	cout << s1 << endl;
	return 0;
}

在这里插入图片描述

4.2 append

在字符串后追加一个字符串。

int main()
{
	string s1("hello");
	s1.push_back(',');
	s1.push_back('w');
	s1.append("orld");
	cout << s1 << endl;
	return 0;
}

在这里插入图片描述
append接口有多个重载函数,可以实现很多方式的字符串尾插。
在这里插入图片描述
甚至可以插入一段迭代器区间

int main()
{
	string s1("hello");
	s1.push_back(',');
	s1.push_back('w');
	s1.append("orld");
	string s2("hello linux");
	s1.append(s2.begin()+5, s2.end());
	cout << s1 << endl;
	return 0;
}

在这里插入图片描述

4.3 operator+=

+=运算符重载可以平替上面两个接口的部分功能,是很常用的。

int main()
{
	string s3("hello");
	s3 += ',';
	s3 += "world";
	cout << s3 << endl;
	return 0;
}

在这里插入图片描述

4.4 不同平台下string的扩容机制

写一段代码来体现一下vs2022的string的扩容机制。

int main()
{	
	string s1;
	size_t old = s1.capacity();
	cout << "capacity:" << old << endl;
	for (size_t i = 0; i < 500; i++)
	{
		s1 += 'x';
		if (s1.capacity() != old)
		{
			cout << "capacity:" << s1.capacity() << endl;;
			old = s1.capacity();
		}
	}
	return 0;

在这里插入图片描述
首次2倍,后续都是1.5倍来扩容的。
Linux下的扩容方式为2倍扩容。
在这里插入图片描述

int main()
{	
	string s3("1111111");
	string s4("11111111111333333333333333333333333333");
	cout << sizeof(s3) << endl;
	cout << sizeof(s4) << endl;
	return 0;
}

在这里插入图片描述
vs的第一次为什么是2倍扩容呢?string的成员变量有一个char buffer[16],当字符的有效个数不大于15的时候,字符串实际存储在栈上,当字符串大于15时,才会到堆上开辟空间,避免了短小字符串在堆上频繁申请增加系统开销。
在这里插入图片描述
在这里插入图片描述
为了避免频繁扩容,在预先知道大约需要多少空间的时候,我们可以调用reserve接口,提前申请好容量。

int main()
{	
	string s5;
	s5.reserve(200);
	cout <<s5.capacity()<< endl;
	return 0;
}

在这里插入图片描述
reserve在vs2022下的实现开出了一个比200大的空间,也是符合使用需求的。

4.5 find和replace 替换指定字符

int main()
{
	string s1("hell  world ");
	cout << s1 << endl;
	size_t i = s1.find(' ');
	while (i != string::npos)
	{
		s1.replace(i, 1, "%%");
		i = s1.find(' ',i+2);
	}
	cout << s1 << endl;
	return 0;

在这里插入图片描述

find没有找到指定字符的时候就是返回npos
find函数由于替换的时候需要循环挪动数据,所以效率很低。

如何提效呢?这里有一个空间换时间的方法。

int main()
{
	string s1("hell  world ");
	cout << s1 << endl;
	string s2;
	for (auto ch : s1)
	{
		if (ch != ' ')
			s2 += ch;
		else
			s2 += "%%";
	}
	s1 = s2;
	//s1.swap(s2);
	cout << s1 << endl;
	return 0;

在这里插入图片描述

5. 剩下的部分接口

5.1 c_str

返回c格式的字符串

int main()
{
	string s1("hello world");
	cout << s1 << endl;
	cout << s1.c_str() << endl;
	return 0;
}

在这里插入图片描述
C++已经实现了流插入运算符的函数重载,可以打印出C格式的字符串了,那为什么还有提供这个接口呢?有些软件不会提供C++的接口。因为C++兼容C,c_str()可以保证C和C++混合编程。

5.2 refind 和 substr 取文件名的后缀

refind: 从字符串的pos位置向前查找,返回该字符在字符串种的位置。
substr:在str中从pos位置开始,截取n个字符,然后将其返回。

int main()
{
   string s1("test.cpp.zip");
   size_t pos = s1.rfind('.');
   if(pos != string::npos)
   {
   	string sub = s1.substr(pos);
   	cout << sub << endl;
   }
   return 0;
}

在这里插入图片描述

相关文章:

  • go游戏后端开发24:写完赢三张游戏
  • C++中如何使用STL中的list定义一个双向链表,并且实现增、删、改、查操作
  • #SVA语法滴水穿石# (012)关于 first_match、throughout、within 的用法
  • 华为交换机配置指南:基础到高级命令详解
  • 51单片机使用定时器实现LCD1602的时间显示(STC89C52RC)
  • 迭代器运算详解(四十二)
  • OSI模型中协议数据单元(PDU)
  • 21 天 Python 计划:MySQL库相关操作
  • 深信服护网蓝初面试题
  • TYUTJava阶段测试
  • Spring 概念
  • 初探:简道云系统架构及原理
  • 定时器的实现方案:红黑树、最小堆与时间轮
  • 【蓝桥杯速成】日期问题(填空题) + 真题讲解 python
  • POSIX线程库
  • 程序化广告行业(65/89):AdX/SSP系统深度剖析与实战要点
  • 《操作系统真象还原》第五章(3)——载入内核
  • JAVA学习-练习试用Java实现“实现一个Hadoop MapReduce任务,对大数据集中的数值进行排序和筛选”
  • 23种设计模式-行为型模式-中介者
  • 可以使用费曼学习法阅读重要的书籍
  • 网站建设合同 费用/域名收录查询工具
  • 做网站的硬件/代刷网站推广链接免费
  • 企业邮箱系统/系统优化的意义
  • 建设银行软件官方网站下载/自建站seo如何做
  • 潍坊网站建设客服/今日国际军事新闻头条
  • 上海 网站平台开发/网站关键词推广优化