首页 > 其他分享 >string类的深度剖析1

string类的深度剖析1

时间:2024-10-31 13:45:47浏览次数:6  
标签:字符 string auto cout 剖析 深度 字符串 size

文章目录

1. 前置语法知识——auto和范围for

1.1 auto关键字

  • 在早期C/C++,auto修饰的变量,是具有自动存储器的局部变量,随着语言的演化,这个逐渐变得鸡肋,于是在C++11中,标准组委会便赋予auto一个全新的含义——auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器。
  • auto声明的变量必须由编译器在编译时期推导而得。

auto使用事项:

  • auto声明指针类型时,auto和auto*没有区别,但声明引用类型,则必须使用auto&
int main()
{
	int n = 100;
	auto p1 = &n;//p1与p2没有区别
	auto* p2 = &n;
	auto& r1 = n;//引用类型必须加 &
	cout << *p1 << " " << *p2 << " " << r1 << endl;//输出100 100 100
	return 0;
}
  • 用auto在同一行声明多个变量时,编译器只会对第一个变量类型进行推导,然后用推导出来的去定义这一行所有变量,故这一行的变量需是同一类型,否则编译报错。
    在这里插入图片描述
  • auto不能做函数参数,但可以做函数返回值,做返回值时也要谨慎使用。
    在这里插入图片描述
    在这里插入图片描述
  • auto不能直接用来声明数组。
    在这里插入图片描述
    auto的用处之一:
    在这里插入图片描述

1.2 范围for

  • 对于一个有范围的集合来说,由程序员去编写代码说明范围往往多余,稍不注意还会出错,基础此,C++11引入了范围for
  • 使用格式:for循环后的括号由冒号 分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,实现自动迭代,自动取数据,自动判断结束。
  • 范围for可以作用到数组和容器对象上进行遍历。
  • 范围for的底层很简单,容器遍历实际就是替换为迭代器
int main()
{
	int arr[] = { 1,2,3,4,5 };
	//1.普通遍历方式:
	cout << "普通遍历方式:";
	for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
	//2.C++11的范围for:
	cout << "C++11的范围for:";
	for (auto n : arr)//用auto自动判断类型
	{
		cout << n << " ";
	}
	cout << endl;
	return 0;
}

在这里插入图片描述

2. string类

2.1 为什么要学string类

  • 在C中表示字符串常常要用字符数组的形式去表示存储,写法上不够方便,使用时也需要直接去管理底层空间,稍不留神就会越界访问
  • 无论是在OJ,还是在常规的工作中,为了方便和可操作性,字符串都是以string的形式出现
  • 详细学习string有助于后续学习STL的其他容器,这些容器之间的接口都大差不差

2.2 认识string类

  • string类属于STL中的容器部分,但string类却是早于STL出现的,STL的其他容器设计也大多仿照过string,在STL出现后,string便被收入进了STL的容器部分。
  • STL的其他容器设计相对更为成熟,是因为它们可以抄string的作业,但string却没有作业可抄,这就导致设计者在设计它的时候不可避免的设计有些冗余。
  • 本文会涉及string的一些常用性的接口,一些重点的接口其名字会标粗
    在这里插入图片描述

2.3 string类的常用接口说明

注意:在使用string类时,必须包含 #include< string > 以及 using namespace std; 为了方便展示,以下接口的说明只给出函数名

2.3.1 常见构造

  • string() —— 构造空的string类对象,即空字符串
  • string(const char*s) —— 用C-string(C格式的字符串) 的形式去构造string对象
  • string(const string& s) —— 拷贝构造函数
  • string(size_t n,char c)——用n个相同的连续字符去构造string对象
#include<iostream>
#include<string>
using namespace std;
int main()
{
	string s1;                //空构造s1
	string s2("hello world!");//用C的字符串去构造s2对象
	string s3(s2);            //用s2对象去拷贝构造s3对象
	cout << " s1对象为:" << s1 << endl;
	cout << " s2对象为:" << s2 << endl;
	cout << " s3对象为:" << s3 << endl;
	return 0;
}

在这里插入图片描述

2.3.2 容量操作

以Visual Studio环境为例,需先明确string类对象的底层设计(这里简单介绍底层,下篇文章会模拟实现string类):

class string
{
public:
	//成员函数...
private:
    char buff[16];           //vs编译器下会先开一个buff数组用来解决短小字符串情景(注意实际可用15个字节,最后一个要放'\0')
	char* _arr = nullptr;    //底层是字符数组,当字符串长度大于15时才会在堆上申请空间
	size_t _size = 0;        //当前有效字符个数
	size_t _capacity = 15;   //当前字符串的容量大小,vs下有buff数组,所以刚开始是15
	static const size_t npos;//表示整型的最大值
};
size_t const string::npos = -1;
  • size——返回当前对象字符串的有效长度
  • length——返回当前对象字符串的有效长度
  • capacity——返回当前为字符串分配的存储空间大小,以字节表示
  • empty——bool类型,判断当前字符串是否为空串,空返回true,非空返回false
//测试size/length/capacity/empty
void TestString1()
{
	string s1;//空字符串
	cout << s1.size() << endl;  //输出0
	cout << s1.length() << endl;//length与size一致,输出0
	cout << s1.capacity() << endl;//输出15
	cout << s1.empty() << endl;  //输出1
	cout << endl;

	string s2("hello world");//非空字符串
	cout << s2.size() << endl;//输出11
	cout << s2.length() << endl;//输出11
	cout << s2.capacity() << endl;//输出15
	cout << s2.empty() << endl;//输出0
}
  • clear——清空当前字符串的有效字符,并让有效长度为0
//测试clear
void TestString2()
{
	string s1("我是一个苦逼的计算机学生,没有对象,没有甜甜的爱情,只有一行行的代码");
	cout << "清空前:";
	//输出68 79 0
	cout << s1.size() << " " << s1.capacity() << " " << s1.empty() << endl;
	s1.clear();
	cout << "清空后:";
	//输出0 79 1---说明clear并不会改变底层的容量大小
	cout << s1.size() << " " << s1.capacity() << " " << s1.empty() << endl;
}
  • reserve——为字符串预留空间
//测试reserve 
void TestString3()
{
	string s2("我是一个苦逼的计算机学生,没有对象,没有甜甜的爱情,只有一行行的代码");
	string s3;
	
	//在已经知道字符串大小的时候可以提前开好空间,避免频繁扩容影响效率
	s3.reserve(100);
	cout << s3.capacity() << endl;//输出111(底层还有个buff数组)
	s3 = s2;
	cout << s2.size() << " " << s2.capacity() << endl;//输出68 79 
	cout << s3.size() << " " << s3.capacity() << endl << endl;//输出68 111 

	//当要预留的空间小于当前容量时,字符串的有效个数,字符串当前的容量都不会改变 
	s3.reserve(0);
	cout << s3.size() << " " << s3.capacity() << endl;//输出68 111
	s3.reserve(85);
	cout << s3.size() << " " << s3.capacity() << endl;//输出68 111
}
  • resize——将原字符串的有效字符的个数(size)改为n,n>size则多出空间会用某个字符填充,n<size则仅改变size
void TestString4()
{
	string s1("我是一个苦逼的计算机学生,没有对象,没有甜甜的爱情,只有一行行的代码");
	cout << s1.size() << " " << s1.capacity() << endl;//输出68 79

	//使有效字符个数减少,底层的容量不变
	s1.resize(24);
	cout << s1 << endl;
	cout << s1.size() << " " << s1.capacity() << endl;//输出24 79

	//当不超过当前容量时,增大有效字符个数,并用字符a填充多出的空间
	s1.resize(75,'a');
	cout << s1 << endl;
	cout << s1.size() << " " << s1.capacity() << endl;//输出75 79

	//当超过当前容量时,先扩容,再增大有效字符个数,并用字符b填充多出的空间
	s1.resize(100, 'b');
	cout << s1 << endl;
	cout << s1.size() << " " << s1.capacity() << endl;//输出90 118
}

在这里插入图片描述
总结:

    1. size()length() 方法只是名字不同,其他完全一致,引入size()的原因是为了与其他容器的接口保持一致,一般都用size()
    1. clear() 只是将string中有效字符清空并将size置为0,不改变底层空间大小。
    1. resize(size_t n)resize(size_t n, char c) 都是将字符串中有效字符个数改变到n个,不同的是 n>size时,resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
    1. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,一般认为当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小,但有些编译器会进行改变容量大小的操作,如Linux下的g++编译器

2.3.3 访问及遍历操作

  • operator[]——重载了[]运算符,返回pos位置的字符
  • begin和end——begin是获取当前容器首字符位置迭代器,end是获取当前容器最后一个字符的下一个位置的迭代器,返回类型为iterator(迭代器类型)
  • rbegin和rend——rbegin是获取 当前容器的最后一个有效字符位置(不是默认\0的位置)反向迭代器,rend是获取当前容器首字符的上一个位置的反向迭代器
int main()
{
	string s1("abcdefghi");
	//正向迭代器变量
	string::iterator itb = s1.begin();
	string::iterator ite = s1.end();
	cout << *itb << endl;      //输出a
	cout << *(ite - 1) << endl;//防止越界,输出i
	//反向迭代器变量
	string::reverse_iterator ritb = s1.rbegin();
	string::reverse_iterator rite = s1.rend();
	cout << *ritb << endl;      //输出i
	cout << *(rite - 1) << endl;//防止越界,输出a
	return 0;
}
  • 范围for——string容器可用范围for去遍历
//string容器的遍历方式
void TestString5()
{
	string s("abcdefghi");
	//普通遍历方式:
	cout << "普通遍历方式:";
	for (int i = 0; i<s.size();i++)
	{ cout << s[i] << " "; }
	cout << endl;
	//C++11的范围for:
	cout << "C++11的范围for:";
	for (auto _s : s)
	{ cout << _s << " "; }
	cout << endl;
	//范围for的底层实质是迭代器
	//正向迭代器遍历--正向打印
	cout << "正向迭代器遍历:";
	auto itb = s.begin();
	while (itb != s.end())
	{
		cout << *(itb) << " ";
		++itb;
	}
	cout << endl;
	//反向迭代器遍历--反向打印
	cout << "反向迭代器遍历:";
	auto ritb = s.rbegin();
	while (ritb != s.rend())
	{
		cout << *(ritb) << " ";
		++ritb;
	}
}

在这里插入图片描述

2.3.4 修改操作

  • push_back——在字符串后尾插某个字符
  • append——在字符串后追加一个字符串
  • operator+=——在字符串后追加一个C_str形式的字符串或字符
//测试push_back/append/+=
void TestString6()
{
	string s1("hms");
	cout << s1 << endl;
	//append
	s1.append(" is very beautiful");
	cout << s1 << endl;
	//push_back
	s1.push_back('!');
	cout << s1 << endl;
	string s2("lm");
	cout << s2 << endl;
	//+=
	s2 += " is very handsome!";
	cout << s2 << endl;
}

在这里插入图片描述

  • c_str——返回C格式的字符串
  • find——从字符串pos位置开始往后找某个字符或字符串,返回其在字符串中的位置
  • rfind——从字符串pos位置开始往前找某个字符,返回该字符在字符串中的位置
  • substr——在str中从pos位置开始,截取n个字符,然后将其返回
void TestSting7()
{
	//c_str
	string s1("shuai_ming");
	cout << s1.c_str() << endl;
	//find
	size_t find = s1.find('_', 0);
	cout << s1[find] << endl;
	//substr
	cout << s1.substr(find) << endl;//默认有个缺省值npos
	cout << s1.substr(find, 5) << endl;
}

在这里插入图片描述

2.3.5非成员函数

  • operator+——单纯+,不改变对象,尽量少用,因为传值返回,导致深拷贝效率低
  • operator>>——输入运算符重载
  • operator<<——输出运算符重载
  • getline——获取一行字符串,直至遇到分隔符,分隔符默认为 \n,也可自己指定分隔符
  • relational operators——诸如> 、<、 +等比较大小的运算符
  • swap——交换两个字符串
void TestString8()
{
	string s1;//空串
	cout << "s1的getline()之前:";
	cout << s1 << endl ;
	cout << "请输入s1的getline()的内容:";
	getline(cin, s1);
	cout << "s1的getline()之后:";
	cout << s1 << endl;
	string s2("hello world!");//非空串
	cout << "s2的getline()之前:";
	cout << s2 << endl;
	cout << "请输入s2的getline()的内容:";
	getline(cin, s2);
	cout << "s2的getline()之后:";
	cout << s2 << endl;
}

在这里插入图片描述

3.结尾

  • string是STL容器的一部分,后面各个容器的学习也大多都有string类似的影子,故学好string类是后续学习STL的基础
  • 本篇文章涉及了string类的大部分知识,还有些小知识及string类的模拟实现将放在下篇文章进行讲解
  • 由于文章篇幅有限,还有很多东西都没有展示出来,感兴趣的读者可以自行阅读C++库的文档(英文版),去补充更多的知识

标签:字符,string,auto,cout,剖析,深度,字符串,size
From: https://blog.csdn.net/2401_84420653/article/details/143318948

相关文章

  • C++(std::to_string())
    目录1.函数定义2.示例代码3.内部实现机制4.注意事项5.应用场景6.使用std::ostringstream控制精度的示例7.总结std::to_string()是C++11引入的一个标准库函数,用于将基本数据类型(如整数、浮点数等)转换为对应的字符串格式。这个函数属于<string>头文件,因此使用时需......
  • CMDB平台(进阶篇):CMDB的应用场景剖析
    配置管理数据库(ConfigurationManagementDatabase,简称CMDB)是IT服务管理(ITSM)中的核心组件。随着信息技术的快速发展,大型企业的IT环境变得越来越复杂,为了更好地管理和维护这些复杂的IT基础设施,近些年来国内CMDB平台越来越多,如乐维CMDB、华为CMDB等。CMDB不仅是一个存储系统,用于记录......
  • c++ string 识别标志位并解析标志位后面的字符
    解析字符串中的固定标志位正则表达式和iterator的配合应用#include<string>#include<map>#include<regex>#include<iostream>//替换\\M+后面的字符//\\M+195B6替换为文std::regexpattern(R"(\\M+[^\\M]*)");//匹配\\M+后跟任意非\\M的字符(0次或多次)......
  • 基于深度学习的舆论分析与检测系统应用与研究
    【1】系统介绍研究背景随着互联网技术的迅猛发展和社会媒体平台的普及,信息传播的速度和范围达到了前所未有的水平。这一变化不仅极大地丰富了人们的社交生活,也为社会科学研究提供了新的视角和工具。舆论分析作为社会科学研究的一个重要分支,其目的是通过收集和分析网络上的......
  • Python 自动化运维:日志与监控的深度探索
    Python自动化运维:日志与监控的深度探索目录......
  • 代码随想录算法训练营第十二天| 226.翻转二叉树、101. 对称二叉树、104.二叉树的最大
    226.翻转二叉树题目链接:.-力扣(LeetCode)文章讲解:代码随想录视频讲解:听说一位巨佬面Google被拒了,因为没写出翻转二叉树|LeetCode:226.翻转二叉树_哔哩哔哩_bilibili《代码随想录》算法公开课开讲啦!快来打卡!本期视频的文字讲解版在「代码随想录」刷题网站:programmercarl.com......
  • DQN——深度Q网络
    目录DQN原理DQN实现代码 代码要点        DQN(DeepQ-Network)是一种深度强化学习算法,结合了Q-learning和神经网络,用于解决复杂的决策问题。它在游戏和控制任务中取得了出色的效果。DQN的关键是利用神经网络来近似Q值函数,使得算法在较高维度的状态空间中也......
  • 专利授权新通道:深度了解专利预审流程与优势
     1.专利预审概念专利预审是一项由各地方知识产权保护中心提供的重要服务,旨在为备案的申请主体提供专利申请的预先审查。这项服务的核心在于,通过知识产权保护中心的预审,符合条件的专利申请可以进入国家知识产权局的快速审查通道,从而大幅度缩短专利授权的周期。根据最新的数据......
  • OpenCV与AI深度学习 | 实战 | YOLO11自定义数据集训练实现缺陷检测 (标注+训练+预测
    本文来源公众号“OpenCV与AI深度学习”,仅用于学术分享,侵权删,干货满满。原文链接:实战|YOLO11自定义数据集训练实现缺陷检测(标注+训练+预测保姆级教程)导 读   本文将手把手教你用YOLO11训练自己的数据集并实现缺陷检测。安装环境YOLO11的介绍和使用这里不再赘......