C++中string类总结二:常用的多种string接口函数的学习

一、string类相关博文


二、string::operator[]

//operator[]实现了两个重载-->系统会去调用最匹配的,差别在返回值
      char& operator[] (size_t pos); //①普通的
const char& operator[] (size_t pos) const;//②const的
  • operator[]的大概实现:在这里插入图片描述
    • 引用做返回的意义:
      • ①减少拷贝(但此处若拷贝,s1[0]字节也才1个,故意义相对不大)
      • ②修改返回对象(s1[0]='x';可将原来s1[0]位上的‘h’,修改成‘x’)==>可读可写每个字符
  • 场景:想遍历string,并每个字符相隔空格地输出
void test_string2()
{
//场景①修改string中特定位置的字符
	string s1("hello world");
	cout << s1[0] << endl;
	s1[0] = 'x';
	cout << s1[0] << endl;
	cout << s1 << endl;

//场景②要求遍历string,每个字符+1
	for (size_t i = 0; i < s1.size(); ++i)
		//for (size_t i = 0; i < s1.length(); ++i)//①
	{
		s1[i]++;
	}
	cout << s1 << endl;

//场景③重载const版本的意义
	const string s2("world");
	for (size_t i = 0; i < s2.size(); ++i)
	{
		//s2[i]++;//不可写
		cout << s2[i] << " ";//只可读
	}
	cout << endl;
	cout << s2 << endl;

	//s2[6];  //内部会检查越界(内部assert断言检查),s2[6]超出了范围
}

三、 .size().length()

  • 这两个虽然在上方(见①处),都能帮助实现遍历(size求个数,length求长度),但推荐使用.size()
  • 原因(究其历史):
    • string的实现是先于STL的产生的,最早是仅有.length()
    • 但为了和后面产生的(如树、链表等)保持一致(如对于链表length勉强合适,但对于树而言.length()便不再合适了),故这些数据结构的容器要求个数,应用.size()更一致。

四、string::at

      char& at (size_t pos);
const char& at (size_t pos) const;
  • operator[]at的区别
    • operator[]越界是会断言,程序直接终止掉
    • at越界是抛异常,捕获后程序还能继续跑
    • (在日常使用中operator[]用的多于at

五、frontback

  • front:返回第一个字符;==> str[0]
  • back:返回最后一个字符;==> str[str.size()-1]
  • 也不怎么需要熟悉,直接用operator[]访问就行

六、在string后加载新的字符、字符串

push_backappendoperator +=

  • append中调用模板的意义在于,未必是string的迭代器,还可以是其他的迭代器(比如vector),只要满足数据类型匹配
void test_string6()
{
	string s("hello");
	s.push_back('-');
	s.push_back('-');
	s.append("world");
	cout << s << endl;//hello--world

	string str("coming");
	s += '@';
	s += str;
	s += "!!!";
	cout << s << endl;//hello--world@coming!!!

//迭代器区间==>了解即可,不怎么使用
	//迭代器区间不一定是string的迭代器,还可以是其他的迭代器(比如vector),只要数据类型匹配
	s.append(++str.begin(), --str.end());
	cout << s << endl;//hello--world@coming!!!omin

	string copy1(++s.begin(), --s.end());
	cout << copy1 << endl;//ello--world@coming!!!omi

	string copy2(s.begin() + 5, s.end() - 5);
	cout << copy2 << endl;//--world@coming!!

	return 0;
}

七、string的增容reserveresize

  • max_size:一般都是写死的固定的,没有什么意义。

  • capacity:容量的大小

    • capacity changed:15中15表示:最初容量实际是16,有一个是给\0\0是标准字符,不属于有效字符)的,剩余15个是能存储有效字符的空间
  • 存在问题:若扩的容量大于需求:浪费空间;若小于需求,需要扩容次数多,频繁扩容

  • 示例:对于同一份代码,vs和g++扩容机制的对比
    vs和g++扩容区别1
    vs和g++扩容区别2

    • 由表格可得结论:数量较大时,Linux(右侧)越扩越少(更接近需求),vs反而越扩越多(多于需求)
    • 🔺由以上示例侧面也可得:不能依赖其底层实现的东西,因为代码要在不同的编译器下跑,它都有可能会有变化,如以下两种情况:
      • 情况1:下方同样代码在vs和Linux下扩容结果不同(结果如上图)
        • VS中对string做了优化:导致第一次扩容近2倍,而后面扩容近1.5倍
          • 它认为你字符串比较短的时候,不断向堆申请小空间的时候,会有一些内存碎片等问题。
          • 故它进行优化,在库的String内部多加了一个char _buff[16]:<16字符串时,存在buff数组中(实际只能最多存15个有效字符);>= 16 存在_str指向堆空间上
          • ==> 优点:小数据时直接放在string中,不会向堆申请;缺点:string会变大
        • 部分Linux中会采取这样的方案:
      • 情况2:无法访问底层的_str,因为在另一个版本下,未必叫_str,也有可能是str_或其他。
  • 对于扩容存在的扩多了和扩少了的问题,解决方式如下:

    • 保留:reserve:开空间
      • 提前预知所需空间,就可运用此接口来提前开好空间,避免扩容,减少消耗,提高效率。单纯只开空间,不会填size大小在这里插入图片描述
      • 但编译器为了对齐,有时也会多给一点(如要s.reserve(1000)会给1007)
      • 区分反转/逆置:reverse
    • resize:开空间 + 初始化
      • 开空间:不仅改变capacity值,也会改变size值。并且默认都初始化为0
      • ![[Pasted image 20221113190430.png]]
      • resize使用示例如下:
//s.resize(1000);//开1000个空间,默认都初始化成0
s.resize(1000, 'x');//开1000个空间,并都初始化成x

八、 string::insert

  • 参考文档:string::insert - C++ Reference (cplusplus.com)
  • 不是特别推荐使用insert,因为其底层是连续的数组,每插入一次,后面的数据就要依次挪动位置==>效率低
  • string::insert的接口介绍:在这里插入图片描述
  • 使用场景:要求实现“在空格位置插入%”的功能
string str("wo lai le");
for(size_t i=0;i<str.size();++i)
{
	if(str[i]==' ')
	{
		str.insert(i,"20%");
		i+=3;//插入后,原空格的位置也往后推了3个位,故i要+3,否则一直检测的都是同一个空格
	}
}
cout<<str<<endl;

九、string::erase

  • string::erase的接口介绍:在这里插入图片描述
  • 使用场景:要求实现“在空格位置插入%,并删除空格”的功能
string str("wo lai le");
for(size_t i=0;i<str.size();++i)//先插入
{
	if(str[i]==' ')
	{
		str.insert(i,"20%");
		i+=3;
	}
}
cout<<str<<endl;
for(size_t i=0;i<str.size();++i)//再删除
{
	if(str[i]==' ')
	{
		str.erase(i,1);
	}
}
cout<<str<<endl;
//以上采用insert和erase的方法效率低,不建议O(N²);也不建议自己去挪动,若自己去挪动就要预估好大小,先resize,再挪动,否则[]会检查是否溢出
//以下采用空间O(N)换时间O(N)的方式,效率更高
string str("wo lai le");
for(size_t i=0;i<str.size();++i) 
{
	if(str[i]!=' ')
	{
		newstr+=str[i];
	}else{
		newstr+="20%";
	}
}
cout<<newstr<<endl;

十、c_str

  • 为了兼容C语言,返回c形式const char*的字符串
string filename("test.cpp");
cout <<filename << endl;//打印方式①走string流的输出
cout <<filename.c_str() << endl;//打印方式②C形式的打印:返回const char*的字符串

FILE* fout = fopen(filename.c_str(),"r");
assert(fout);
char ch = fgetc(fout);
while (ch != EOF)
{
	cout << ch;
	ch = fgetc(fout);
}
  • 如上中,打印方式①和②一般情况下都是一样的,但在如下+=后就有区别了
    • ③因为string对象,是以size为准的
      • 监视窗口此处也有个bug,只显示到test.cpp,但查看详细时,就能看到所有的在这里插入图片描述
      • ④常量字符串对象,是以\0为准
string filename("test.cpp");
filename += '\0';
filename += "string.cpp";
cout << filename << endl;//③test.cpps tring.cpp
cout << filename.c_str() << endl;//④test.cpp

十一、 findrfind

  • 使用场景①:要求实现“找到字符串str中的某个字符”的功能
string filename("test.cpp.tar.zip");
//size_t pos = filename.find('.');//从左到右找第一个后缀.cpp
size_t pos = filename.rfind('.');//从右到左找第一个后缀.zip
if(pos != string::npos)
{
	//string suff = filename.substr(pos,filename.size()-pos);
	string suff = filename.substr(pos);//默认取到末尾
	cout<<suff<<endl;//.cpp
}
  • 使用场景②:把网址切割成协议、域名、动作三部分
    • 分析图例:在这里插入图片描述
void DealUrl(const string& url)
{
//部分1:协议
	size_t pos1 = url.find("://");
	if (pos1 == string::npos)
	{
		cout << "非法url" << endl;
		return;
	}

	string protocol = url.substr(0, pos1);
	cout << protocol << endl;

//部分2:域名
	size_t pos2 = url.find('/', pos1 + 3);//从pos1 + 3往后找第一个'/'
	if (pos2 == string::npos)
	{
		cout << "非法url" << endl;
		return;
	}
	string domain = url.substr(pos1 + 3, pos2 - pos1 - 3);//取pos2-pos1-3长度
	cout << domain << endl;

//部分3:动作
	string uri = url.substr(pos2 + 1);//从pos2 + 1位置往后取到结尾
	cout << uri << endl << endl;
}

int main()
{
	string url1 = "https://cplusplus.com/reference/string/string/";
	string url2 = "ftp://ftp.cs.umd.edu/pub/skipLists/skiplists.pdf";

	DealUrl(url1);
	DealUrl(url2);

	return 0;
}

十二、string相关的一些类型转换

1、回顾

2、string中的to_string

3、stoi

  • 把字符串转回成整型
  • 部分演示:
void test_string()
{
//转成字符串
	int ival=1234;
	double dval=12.34;
	string istr = to_string(ival);
	string dstr = to_string(dval);
	cout << istr << endl;//1234
	cout << dstr << endl;//12.340000,c++默认保留小数点后6位的精度

//把字符串转回整型和浮点型
	istr = "9999";
	dstr = "9999.99";
	ival = stoi(istr);//9999
	dval = stod(dstr);//9999.9899999999998是因为有些浮点数(如dstr)无法精确存储,精度存进去可能有精度损失,只能无限精确、保留多少位
}

祝大家学习愉快 : )

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值