STL—容器—string类【对其结构和使用的了解】【对oj相关练习的训练】

STL—容器—string类

其实string类准确来说并不是容器,因为他出现的时间比STL要早,但是也可以说是容器吧。

1.为什么要学习string类?

1.1C语言当中的字符串

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

1.2面试题

415. 字符串相加 - 力扣(LeetCode)

在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本

都使用string类,很少有人去使用C库中的字符串操作函数。

2.标准库中的string类

2.1string类的了解

string类的文档介绍

  1. 字符串是表示字符序列的类

  2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。

  3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信息,请参阅basic_string)。

  4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。

  5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。

总结:

  1. string是表示字符串的字符串类

  2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。

  3. string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator> string;

  4. 不能操作多字节或者变长字符的序列。

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

我们在包含头文件的时候经常会遇到两个比较容易混淆的头文件引用#include<string.h> 和 #include ,两者的主要区别如下:

#include<string.h>是C语言的标准库,主要是对字符串进行操作的库函数,是基于char*进行操作的,例如常见的字符串操作函数stpcpy、strcat都是在该头文件里面声明的。

#include是C++语言的标准库,该库里面定义了string类,你可以包含这个头文件,然后定义一个字符串对象,对于字符串的操作就基于该对象进行,例如:string str;

一般来说,c++的头文件不会有.h的出现,比如

————————————————

文档方面,个人觉得这个比较好用

- C++ Reference

作为一个程序员,查文档是一个必须具备的基本素质,当遇到一个不熟悉的接口需要使用的时候,查文档就是最好的解决方法,因此我们需要学会怎么查文档。

2.2string类的常用接口说明

2.2.1.string类的常见构造

image-20240617000544460

c++98版本中全部的构造

image-20240617002205885

实例:

// 初识string类 
// string类 有很多的接口和成员函数,我们主要得熟悉的使用其中的2成,剩下的8成需要我们去通过网络查询来解决
# include<string>
# include<iostream>

using namespace std;
int main()
{
	// 关于string类的构造

	string s1; // 构造
	string s2("string"); // 直接构造
	string s3(s2); // 拷贝构造
    // 上面这三个最常用
    
	string s4(10, 's'); // string类中的的不常用的特殊构造
	string s5("hello", 1, 3); // string类中的不常用的特殊构造 [这里如果给的不是3 是过大的值,那么就是npos,意思就是将字符串后面有多少给多少] 这些知识都可以查文档查到
    

	// 能使用 << 来输出是因为 库中 已经对<< 进行了重载
	cout << s1 << endl; // [这里默认是空串]
	cout << s2 << endl; // string
	cout << s3 << endl; // string
	cout << s4 << endl; // ssssssssss
	cout << s5 << endl; // ell [取"hello"中的 第 1~3号元素来构造的, 正好就是ell]
	
	string s6 = s4; // 这里还是拷贝构造
	string s7 = "string"; // 这里正常来说就是发生隐式类型转化 意味着,先直接构造,在拷贝构造
	// 但是这里的直接构造 + 拷贝构造发生在了同一表达式,因此编译器直接优化成直接构造

	cout << s6 << endl; // ssssssssss
	cout << s7 << endl; // string

	return 0;
}
2.2.2string类对象的容量操作

常见的与容量相关的接口:

image-20240617005128425

接口的代码使用:

1.size(重点)

size可以统计字符串的有效字符长度并返回。一般来说会用作遍历。

// string类对象有关容量的常见接口
// 1.size()
# include<string>
# include<iostream>
using namespace std;

int main()
{	
	string s("hello");
	cout << "s: ";
	// 遍历
	for (size_t i = 0; i < s.size(); i++)
	{
		s[i] += 1; // 写 [这个相当于对取出来的字符+1,就相当于给其ASCII码值+1]
	}

	for (size_t i = 0; i < s.size(); i++)
	{
		cout << s[i]; // 读
	}
	cout << endl; //s8: ifmmp [hello每个字符的ASCII码值分别+1]



	return 0;
}
2.length

length也会统计字符串的有效长度并返回。其实和size是非常像的,甚至其底层的实现原理都是一样的、

其实在早期的时候用的都是length,但是后面数据结构都用size了,就统一用size了,length和size可以说就是一样的,这里推荐使用size

// 2.length

# include<string>
# include<iostream>
using namespace std;

void test_string()
{
	string s1("hello, world");
	cout << s1.size() << endl; // 12
	cout << s1.length() << endl; // 12

}

int main()
{
	test_string();
	return 0;
}
3.capacity

capacity可以返回当前空间的总大小

// 3.capacity
# include<string>
# include<iostream>
using namespace std;

int main()
{
	string s("hello, world");
	cout << s.capacity() << endl; // 15

	// 目前s有12个空间存放数据,如果超出空间,会增容多少呢?
	s += "ddddd";
	cout << s.capacity() << endl; // 31
	// 可以发现,直接将其增容到了2倍的容量。

	return 0;
}
4.clear(重点)

清空字符串内容 【注意,空间不会被销毁,只是内容被清空了】

// 4.clear

# include<string>
# include<iostream>
using namespace std;

int main()
{
	string s("hello, world");
	cout << s << endl; // hello, world

	s.clear();
	cout << s << endl; // 空串

	// 我们清除了字符串内容,不代表空间被销毁了、
	cout << s.capacity() << endl; // 15

	return 0;
}
5.empyt(重点)

empty可以检测字符串是否为空串,是就返回true,不是就返回false

//5.empty

# include<string>
# include<iostream>
using namespace std;

int main()
{
	string s("hello, world");
	
	if (s.empty())
		cout << "该string类对象为空" << endl;
	else
		cout << "该string类对象不为空" << endl;

	s.clear();

	if (s.empty())
		cout << "该string类对象为空" << endl;
	else
		cout << "该string类对象不为空" << endl;

	return 0;
}
6.reserve/resize(重点)

在学习这两个接口之前,我们先来看一个代码:

这个代码可以观察到每次增容时容量的变化大小

# include<string>
# include<iostream>
using namespace std;

int main()
{
	string s;
	size_t sz = s.capacity();
	cout << "s_capacity grow\n";
	for (int i = 0; i < 100; i++)
	{
		s.push_back('c'); // 也可以s += 'c'
		if (sz != s.capacity()) // != 的时候就说明s进行了增容
		{
			sz = s.capacity(); // 将新容量给到sz
			cout << "new capacity: " << sz << endl; // 将此时的容量打印出来
		}
	}

	return 0;
}

image-20240617164339500

我们发现是接近1.5倍增容的。但是我们之前知道15容量在增容的时候是按两倍增容增到31的、

规律是:空间基数越大,增容的倍数越小

因为增容是有代价的。增容越多的空间,执行越多次增容,代价就越大。

知道了这个小知识,我们就可以来看resize和reserve了。

reserve给字符串预留空间。

其实就是给字符串开辟空间

// 6.resize/reserve
# include<string>
# include<iostream>
using namespace std;

int main()
{
	string s;
	s.reserve(100); // 我们知道s需要100个空间,提前开好空间

	size_t sz = s.capacity();
	cout << "s_capacity grow\n";
	for (int i = 0; i < 100; i++)
	{
		s.push_back('c'); // 也可以s += 'c'
		if (sz != s.capacity()) // != 的时候就说明s进行了增容
		{
			sz = s.capacity(); // 将新容量给到sz
			cout << "new capacity: " << sz << endl; // 将此时的容量打印出来
		}
	}

	return 0;
}

image-20240617165039849

由于我们提前开辟好了100个空间【实际上不一定会开100个,因为要考虑内存对齐,最后还要放个\0】,s没有再执行增容操作。

这样我们只增容了一次,比起自动增容的次数就减少了许多。

image-20240617165453007

直接开辟了111个空间,并不是我们输入的100。【了解一下】

resize:在reserve的基础上,将有效字符的个数修改,并填充字符

int main()
{
	string s;
	//s.reserve(100); // 我们知道s需要100个空间,提前开好空间
	s.resize(100); // 将有效字符个数修改到100,并填充\0

	size_t sz = s.capacity();
	cout << "s_capacity grow\n";
	for (int i = 0; i < 100; i++)
	{
		s.push_back('c'); // 也可以s += 'c'
		if (sz != s.capacity()) // != 的时候就说明s进行了增容
		{
			sz = s.capacity(); // 将新容量给到sz
			cout << "new capacity: " << sz << endl; // 将此时的容量打印出来
		}
	}

	return 0;
}

image-20240617165704803

可以看到它相当于reserve(100)并且将有效字符个数修改到了100,并且填充了\0、

image-20240617171034866

由于前100个空间被resize填充了\0,因此后面还是会执行增容操作

因此如果想后面不执行增容操作,这里要用reserve

我们还可以指定填充的字符

int main()
{
	string s;
	//s.reserve(100); // 我们知道s需要100个空间,提前开好空间
	//s.resize(100); // 将有效字符个数修改到100,并填充\0
	s.resize(100,'6'); // 将有效字符修改到100,并填充6
    // 因此如果想后面不执行增容操作,这里要用reserve

	size_t sz = s.capacity();
	cout << "s_capacity grow\n";
	for (int i = 0; i < 100; i++)
	{
		s.push_back('c'); // 也可以s += 'c'
		if (sz != s.capacity()) // != 的时候就说明s进行了增容
		{
			sz = s.capacity(); // 将新容量给到sz
			cout << "new capacity: " << sz << endl; // 将此时的容量打印出来
		}
	}
	
	return 0;
}

image-20240617165942993

可以看到不再填充\0而是填充我们指定的字符6

image-20240617170946266

并且由于前100个空间都被resize填入了元素,因此后面插入100个c的时候还是会增容的

因此如果想后面不执行增容操作,这里要用reserve

总结(重要):

string容量相关方法使用代码演示

  1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
  2. clear()只是将string中有效字符清空,不改变底层空间大小
  3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变
string s1("hello, world");
s1.resize(5); // 会将原来的size减小到 5。
cout << s1 << endl; // hello

s1.resize(20, 'x'); // 将size改到20,由于前5个已经有元素了,后15个填入x【空间不够会自动扩容】
cout << s1 << endl; // helloxxxxxxxxxxxxxxx
  1. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小
注意:
  1. 上面我们讲的有关容量的接口都是常用的,并不是全部。全部的接口可以去文档中查看
  2. 在查看接口的时候,目前来说只需要查看接口如何使用,不用查看接口的底层实现,因为以目前的水平,是完全看不懂的。
  3. 常用的接口需要熟悉使用、

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

image-20240617183351615

string中元素访问及遍历代码演示

下面我们对于遍历一共有3种方法【[] + 下标】【迭代器】【范围for】

拓展:迭代器

我们来了解一下迭代器

image-20240617120225600

如图所示,迭代器一共四种,这里我们就是了解一下,具体的我们后面会详细学习。

遍历的代码:

void test_string3()
{
	// 迭代器一共有4种迭代器,这里在介绍一种
	string s("hello world");
	// 倒着遍历
	string::reverse_iterator rit = s.rbegin(); //要用rbegin,指向字符串的最后一个有效元素
    // auto rit = s.rbegin();
	while (rit != s.rend()) // rend,指向字符串的首元素
	{
		cout << *rit;
		rit++; // 其实是倒着走的
	}
	cout << endl;// dlrow olleh
}

int main()
{	
	// [] + 下标  【平常推荐使用这个】
	string s("hello");
	cout << "s: ";
	// 遍历
	for (size_t i = 0; i < s.size(); i++)
	{
		s[i] += 1; // 写 [这个相当于对取出来的字符+1,就相当于给其ASCII码值+1]
	}

	for (size_t i = 0; i < s.size(); i++)
	{
		cout << s[i]; // 读
	}
	cout << endl; //s8: ifmmp [hello每个字符的ASCII码值分别+1]

	// 迭代器【每个容器都会有自己的迭代器】【迭代器一共有四种】(这里了解一下迭代器)
	string::iterator it = s.begin(); // string::iterator 实际是一个类型
	//auto it = s1.begin(); // 也可以这样写 [自动推演]
	// 遍历
	while (it != s.end())
	{
		*it -= 1; // 写
		++it; // 迭代
	}

	it = s.begin(); // 重置
	while (it != s.end())
	{
		cout << *it; // 读
		++it; // 迭代
	}
	cout << endl;// hello

	test_string3(); // 另外一种迭代器的使用
	// 上述是迭代器的使用方法,熟悉一下就行,后面会详细学习。

	// 范围for(c++11)  [原理其实就是迭代器]
	for (auto ch : s) // 自动把s8从左到右依次给到ch auto会自动推断类型
	{
		cout << ch;
	}
	cout << endl; //hello

	return 0;
}

再来看一个场景:

  • 将字符串转化为整数
// 将传进来的字符串转化成整数 【使用迭代器】
int string2int(const string& str)// 用引用提高效率,并且不改变str,const修饰
{
	int val = 0;
	string::const_iterator cit = str.begin(); // 此时的str是const对象,返回的是const类型的迭代器类型
	while (cit != str.end())
	{
		val *= 10;
		val += (*cit - '0');
	}
	cout << val << endl;
}

int main()
{
	// 将字符串转化成整数
	string s("12345");
	int val = 0; 
	for (int i = 0; i < s.size(); i++) // 遍历
	{
		// 让val + 上取出来的数字,然后再*10, 循环,直至全部取出、
		val *= 10; 
		val += (s[i] - '0'); // -'0'是因为 取出来的是字符 而不是整形, - 去'0'的ASCII值才是对应整形
	}
	cout << val << endl; // 12345
    
	string s1("12345");
	int ret = string2int(s1);
	cout << ret << endl; // 12345
	return 0;
}

通过上面两个例子,我们了解到了遍历有不同的方式去遍历。

在这里面当中,迭代器似乎是一个比较麻烦的使用方式。

从方向上分:正向和反向迭代器

从权限上分:普通迭代器,const迭代器。

因此使用的时候似乎不吃香,从短期来看确实是如此,因为经过了c语言的学习,我们已经习惯于用数组的方式去遍历。但是数组这个方式只能用于vector 和 string。等到后面更多的数据结构,也就是容器出现,迭代器就会频繁使用,因为+所有容器的迭代器使用方法都是类似的。

因此迭代器的学习是c++中一个重要的部分。迭代器可以用于所有STL库中的容器的遍历

2.2.4string类对象的修改操作

常见的几个与修改操作相关的接口

image-20240617194843211

1.push_back/append/+=(重要)

push_back:在字符串后边尾插字符

append:在字符串后边追加字符串

+=:在字符串后边追加字符串

这三个我们推荐**+=**

有关尾插的三个接口的使用的代码:

// 1.+= / push_back / append 【有关尾插的三个接口】
# include<string>
# include<iostream>
using namespace std;

int main()
{
	string s1;
	
	s1.push_back('p');
	s1 += 'p'; // 推荐使用+=
	cout << s1 << endl; //pp  可以看到 += 和 调用尾插接口的结果是一样的

	s1 += "hello"; // 这里调用的是另外一个 += 的重载函数
	cout << s1 << endl; // pphello

	s1.append(" world"); // 在s1后尾插" world"字符串
	cout << s1 << endl; // pphello world

	// 在上述三个尾插的操作接口中,我们推荐使用+=

	return 0;
}

如果想在具体位置插入的话可以使用insert接口,想删除具体位置的话用erase接口

可以上文档查具体使用方法,这里不做详解

2.c_str

image-20240618130116327

c_str: 会将string类对象以const char 的格式返回*

他会在string类对象中找到\0 ,把\0之前的字符串内容当做一个 const char 类型返回*

代码操作:

//2.c_str
# include<string>
# include<iostream>
using namespace std;

int main()
{
	string s("hello");
	
	const char* str = s.c_str();// str拿到的是const char* 类型。
	// 获取字符数组的首地址,用c语言中的字符串形式去遍历
	while (*str) // 只要*str不是\0就继续
	{
		cout << *str;
		str++;
	}
	cout << endl; // hello

	// 也可以直接输出
	cout << s << endl; // hello
	// 这里调用的是string类中的重载的<<, 它会将整个s进行输出

	cout << s.c_str() << endl; // hello
	// 这里相当于直接对 const char* 进行输出,遇到\0就会结束输出

	s += '\0';
	s += ' ';
	s += "world";

	cout << s << endl; // hello world  中间的\0是不可见字符,不会打印出来

	cout << s.c_str() << endl; // hello  因为从h到 \0 被当做了一个const char*,s.c_str()返回的就是 hello

	return 0;
}
拓展:编码表

我们知道一个中文占据两个字节,这是由编码表实现的,因为中文的编码表是GDK

而英文的编码表是ASCII表

编码的本质是:映射,将对应的值映射到表中的某一个位置。

我们来看一段代码:

// 编码表
# include<string>
# include<iostream>
using namespace std;

int main()
{
	// 我们知道英文是用ASCII码表来编码的

	string s = "中国"; // 中文使用GDK来编码的
	cout << s << endl; //中国

	s[3] = -7;
	cout << s << endl; // 中郭

	s[3] = -8;
	cout << s << endl; // 中锅

	s[3] = -9;
	cout << s << endl; // 中棍


	return 0;
}

下图是s字符串中存储的信息:

image-20240618234637031

可以很清楚的看到中文是两个字节共同编码成一个中文。

3.find/rfind/substr(重要)

image-20240619005428685

find: 可以查询字符串中的某个字符

如果找不到会返回npos【rfind找不到也一样返回npos】

image-20240620170550769

npos是-1 size_t的-1相当于int类型的最大值,42亿多

image-20240620171505713

substr:可以返回指定位置以后得字符串。如果不指定就是默认从头开始

来看一段代码:

//3.find/rfind
# include<string>
# include<iostream>
using namespace std;

int main()
{
	string s("text.cpp"); // 如果我们想查询这个后缀.cpp
	// 我们就可以用find去查询.  再将其后边的打印出来
	size_t pos = s.find('.'); // pos = 4
	if (pos != string::npos) // npos是-1  size_t的-1相当于int类型的最大值,42亿多
	{
		// 用substr接口实现.后的打印
		cout << s.substr(pos) << endl; // .cpp
		// s.substr(pos)就是打印pos之后的数据,包括pos
	}

	return 0;
}

rfind:可以查询字符串中的一个字符,并且是最后一次出现的

//3.find/rfind
# include<string>
# include<iostream>
using namespace std;

int main()
{
	string s("text.cpp"); // 如果我们想查询这个后缀.cpp
	// 我们就可以用find去查询.  再将其后边的打印出来
	size_t pos = s.find('.'); // pos = 4
	if (pos != string::npos) // npos是一个-1  size_t的-1相当于int类型的最大值,42亿多
	{
		// 用substr接口实现.后的打印
		cout << s.substr(pos) << endl; // .cpp
		// s.substr(pos)就是打印pos之后的数据,包括pos

	}

	// 还会有一种情况,如果想查询的字符串有多个后缀,我们想输出最后一个后缀的话
	// 我们就可以用rfind 重复的查找.  找到最后一个.
	string s1("test.cpp.zip"); // pos1 = 8
	size_t pos1 = s1.rfind('.');
	if (pos1 != string::npos)
	{
		cout << s1.substr(pos1) << endl; // .zip

	}

	return 0;
}
拓展:网址

平常我们看到的网址是由协议、域名、资源名称

// 网址
# include<string>
# include<iostream>
using namespace std;

// 协议、域名、资源名称
int main()
{
	string url("https://gitee.com/wzf-sang");
	//https是协议 http和https的差别就是https是加密的
	///gitee.com其实是个ip但是绑定了域名。如果不绑定域名这里是个ip【域名更好记住】
	//wzf-sang就是资源名称。

	
	//【在我们输入网址敲回车之后,服务器做的第一件事就是将你输入的网址拆成协议、域名、资源名称】
	//【因此才能在对应协议下申请访问对应的域名并根据资源名称返回资源】
	// 现在有个要求:分离出url。
	size_t i1 = url.find(':'); // i1 = 5
	if (i1 != string::npos)
	{
		cout << url.substr(0, i1) << endl; // https
		//从0这个下标,输出i1个元素
	}

	size_t i2 = url.find('/', i1 + 3); // 从i1 + 3开始找
	if (i2 != string::npos)
	{
		cout << url.substr(i1 + 3, i2 - i1 - 3) << endl;// gitee.com
		// 从i1 + 3这个下标,输出i2 - i1 - 3个元素
	}

	cout << url.substr(i2 + 1) << endl; //wzf-sang

	return 0;
}

而我们可以将其功能分离出来,抽象成一个函数

// 网址
# include<string>
# include<iostream>
using namespace std;

// 该函数用于分离网址的三个必要信息
void spilt_url(const string& url)
{
	//【在我们输入网址敲回车之后,服务器做的第一件事就是将你输入的网址拆成协议、域名、资源名称】
	//【因此才能在对应协议下申请访问对应的域名并根据资源名称返回资源】
	// 现在有个要求:分离出url。
	size_t i1 = url.find(':'); // i1 = 5
	if (i1 != string::npos)
	{
		cout << url.substr(0, i1) << endl; // https
		//从0这个下标,输出i1个元素
	}

	size_t i2 = url.find('/', i1 + 3); // 从i1 + 3开始找
	if (i2 != string::npos)
	{
		cout << url.substr(i1 + 3, i2 - i1 - 3) << endl;// gitee.com
		// 从i1 + 3这个下标,输出i2 - i1 - 3个元素
	}

	// 在i2这个下标后面的都是资源名称
	cout << url.substr(i2 + 1) << endl; // wzf-sang
	// + 1是因为i2是 / ,i2后面的才是资源名称
}


// 协议、域名、资源名称
int main()
{
	string url("https://gitee.com/wzf-sang");
	string url2("https://leetcode.cn/problems/majority-element/description/?envType=study-plan-v2&envId=top-interview-150");
	//https是协议 http和https的差别就是https是加密的
	///gitee.com其实是个ip但是绑定了域名。如果不绑定域名这里是个ip【域名更好记住】
	//wzf-sang就是资源名称。

	spilt_url(url);
	//	https
	//	gitee.com
	//	wzf - sang

	spilt_url(url2);
	//https
	//leetcode.cn
	//problems/majority-element/description/?envType=study-plan-v2&envId=top-interview-150

	return 0;
}

2.2.5string类非成员函数

image-20240619011949494

上面的几个接口了解一下,下面的OJ题目中会有一些体现他们的使用。string类中还有一些其他的操作,这里不一一列举,在需要用到时不明白了查文档即可。

2.2.6.vs和g++下string结构的说明

注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。

  • vs下string的结构

string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字

符串的存储空间

当字符串长度小于16时,使用内部固定的字符数组来存放

当字符串长度大于等于16时,从堆上开辟空间

这种设计是合理的,大多数情况下字符串的长度都小于16,那string对象创建好之后,内

部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。

其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量

最后:还有一个指针做一些其他事情。

故总共占16+4+4+4=28个字节

image-20240705222701207

  • g++下string的结构

G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指

针将来指向一块堆空间内部包含了如下字段:

  1. 空间总大小

  2. 字符串有效长度

  3. 引用计数

struct _Rep_base
{
 	size_type _M_length;
 	size_type _M_capacity;
 	_Atomic_word _M_refcount;
};
  1. 指向堆空间的指针,用来存储字符串。

2.2.7与string相关的oj题目

  1. 917. 仅仅反转字母 - 力扣(LeetCode)

  2. 387. 字符串中的第一个唯一字符 - 力扣(LeetCode)

  3. 字符串最后一个单词的长度

  4. 125. 验证回文串 - 力扣(LeetCode)

  5. 415. 字符串相加 - 力扣(LeetCode)

  6. 541. 反转字符串 II - 力扣(LeetCode)

  7. 557. 反转字符串中的单词 III - 力扣(LeetCode)

  8. 43. 字符串相乘 - 力扣(LeetCode)较难

  9. 找出字符串中第一个只出现一次的字符较难

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/778668.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【技术支持】vscode代码格式化空格数量问题

问题 使用AltShiftF代码格式化时&#xff0c;发现有些文件格式化后缩进为2格个空格&#xff0c;有些文件正常4个空格 刨析 发现vue创建的文件使用的是两个空格&#xff0c;而且换行符表示方式也不一样 LF 是 Unix 和 Unix-like 系统&#xff08;如 Linux 和 macOS&#xff0…

springcloud分布式架构网上商城 LW +PPT+源码+讲解

3系统分析 3.1可行性分析 在开发系统之前要进行系统可行性分析&#xff0c;目的是在用最简单的方法去解决最大的问题&#xff0c;程序一旦开发出来满足了用户的需要&#xff0c;所带来的利益也很多。下面我们将从技术、操作、经济等方面来选择这个系统最终是否开发。 3.1.1技术…

网络漏洞挖掘实测报告

关于作者&#xff1a;个人主页 网络漏洞挖掘实测报告 一、前言 网络漏洞挖掘是信息安全领域中至关重要的一环。通过挖掘和修复漏洞&#xff0c;可以有效地保护系统免受潜在的攻击和破坏。本报告旨在记录一次完整的网络漏洞挖掘实测过程&#xff0c;包括实施方法、过程、结果以…

欢迎加入国家智能网联汽车创新中心OS开发训练营大家庭

欢迎加入国家智能网联汽车创新中心OS开发训练营大家庭。&#x1f680; 导学阶段启动 在正式开营之前&#xff0c;我们特别设置了导学阶段&#xff0c;旨在帮助大家更好地迎接颇具挑战性的项目实战。导学阶段包括一系列精心准备的视频课程和配套习题。github链接&#xff1a;htt…

基于YOLOv10+YOLOP+PYQT的可视化系统,实现多类别目标检测+可行驶区域分割+车道线分割【附代码】

文章目录 前言视频效果必要环境一、代码结构1、 训练参数解析2、 核心代码解析1.初始化Detector类2. torch.no_grad()3. 复制输入图像并初始化计数器4. 调用YOLOv10模型进行目标检测5. 提取检测结果信息6. 遍历检测结果并在图像上绘制边界框和标签7. 准备输入图像以适应End-to-…

2024年导游资格证题库备考题库,高效备考!

1.台湾著名的太鲁阁公园的特色是&#xff08;&#xff09;。 A.丘陵和溶洞 B.森林和瀑布 C.峡谷和断崖 D.彩林和彩池 答案&#xff1a;C 解析&#xff1a;台湾著名的太鲁阁公园的特色是峡谷和断崖。 2.下列位于台湾的景区中&#xff0c;素有"神秘的森林王国"之…

DropNotch for Mac v1.0.1 在 Mac 刘海快速使用 AirDrop

应用介绍 DropNotch 是一款专为Mac设计的应用程序&#xff0c;可以将MacBook的凹口区域&#xff08;刘海&#xff09;转换为文件放置区。 功能特点 文件共享: 用户可以将文件拖放到MacBook的凹口区域&#xff0c;并通过AirDrop、邮件、消息等方式轻松共享。多显示器支持: 即…

Web漏洞扫描工具AppScan与AWVS测评及使用体验

AppScan和AWVS业界知名的Web漏洞扫描工具&#xff0c;你是否也好奇到底哪一个能力更胜一筹呢&#xff1f;接下来跟随博主一探究竟吧。 1. 方案概览 第一步&#xff1a;安装一个用于评测的Web漏洞靶场&#xff08;本文采用最知名和最广泛使用的靶场&#xff0c;即OWASP Benchma…

WY-35A4T三相电压继电器 导轨安装 约瑟JOSEF

功能简述 WY系列电压继电器是带延时功能的数字式交流电压继电器。 可用于发电机&#xff0c;变压器和输电线的继电保护装置中&#xff0c;作为过电压或欠电压闭锁的动作元件 LCD实时显示当前输入电压值 额定输入电压Un:100VAC、200VAC、400VAC产品满足电磁兼容四级标准 产品…

Spring容器Bean之XML配置方式

一、首先看applicationContext.xml里的配置项bean 我们采用xml配置文件的方式对bean进行声明和管理&#xff0c;每一个bean标签都代表着需要被创建的对象并通过property标签可以为该类注入其他依赖对象&#xff0c;通过这种方式Spring容器就可以成功知道我们需要创建那些bean实…

使用LoFTR模型进行图像配准、重叠区提取

LoFTR模型源自2021年CVPR提出的一篇论文LoFTR: Detector-Free Local Feature Matching with Transformers&#xff0c;其基于pytorch实现图像配准&#xff0c;与基于superpointsuperglue的方法不同&#xff0c; 是一个端到端的图像配准方法。与LoFTR官方库相关的有loftr2onnx库…

NDVI数据集提取植被覆盖度FVC

植被覆盖度FVC 植被覆盖度&#xff08;Foliage Vegetation Cover&#xff0c;FVC&#xff09;是指植被冠层覆盖地表的面积比例&#xff0c;通常用来描述一个区域内植被的茂密程度或生长状况。它是生态学、环境科学以及地理信息系统等领域的重要指标&#xff0c;对于理解地表能…

SwinTransformer的相对位置索引的原理以及源码分析

文章目录 1. 理论分析2. 完整代码 引用&#xff1a;参考博客链接 1. 理论分析 根据论文中提供的公式可知是在 Q Q Q和 K K K进行匹配并除以 d \sqrt d d ​ 后加上了相对位置偏执 B B B。 A t t e n t i o n ( Q , K , V ) S o f t m a x ( Q K T d B ) V \begin{aligned} &…

软件设计之Java入门视频(12)

软件设计之Java入门视频(12) 视频教程来自B站尚硅谷&#xff1a; 尚硅谷Java入门视频教程&#xff0c;宋红康java基础视频 相关文件资料&#xff08;百度网盘&#xff09; 提取密码&#xff1a;8op3 idea 下载可以关注 软件管家 公众号 学习内容&#xff1a; 该视频共分为1-7…

gptoolbox matlab工具箱cmake 调试笔记

一、问题描述 起因&#xff1a;在matlab中运行Offset surface of triangle mesh in matlab的时候报错&#xff1a; 不支持将脚本 signed_distance 作为函数执行: E:\MATLAB_File\gptoolbox\mex\signed_distance.m> 出错 offset_bunny (第 22 行) D signed_distance(BC,V,F…

绝区贰--及时优化降低 LLM 成本和延迟

前言 大型语言模型 (LLM) 为各行各业带来了变革性功能&#xff0c;让用户能够利用尖端的自然语言处理技术处理各种应用。然而&#xff0c;这些强大的 AI 系统的便利性是有代价的 — 确实如此。随着 LLM 变得越来越普及&#xff0c;其计算成本和延迟可能会迅速增加&#xff0c;…

Python实战训练(方程与拟合曲线)

1.方程 求e^x-派&#xff08;3.14&#xff09;的解 用二分法来求解&#xff0c;先简单算出解所在的区间&#xff0c;然后用迭代法求逼近解&#xff0c;一般不能得到精准的解&#xff0c;所以设置一个能满足自己进度的标准来判断解是否满足 这里打印出解x0是因为在递归过程中…

经典双运算放大器LM358

前言 LM358双运放有几十年的历史了吧&#xff1f;通用运放&#xff0c;很常用&#xff0c;搞电路的避免不了接触运放&#xff0c;怎么选择运放&#xff0c;是工程师关心的问题吧&#xff1f; 从本文开始&#xff0c;将陆续发一些常用的运放&#xff0c;大家选型可以参考&#…

【最新整理】全国高校本科及专科招生和毕业数据集(2008-2022年)

整理了各省高校本科、专科招生和毕业数据等21个相关指标&#xff0c;包括招生、在校、毕业人数&#xff0c;以及财政教育支出、教育经费等数据。含原始数据、线性插值、回归填补三个版本&#xff0c;希望对大家有所帮助 一、数据介绍 数据名称&#xff1a;高校本科、专科招生…

如何处理 PostgreSQL 中由于表连接顺序不当导致的性能问题?

文章目录 一、理解表连接和连接顺序二、识别由于表连接顺序不当导致的性能问题三、影响表连接顺序的因素四、解决方案手动调整连接顺序创建合适的索引分析数据分布和优化查询逻辑 五、示例分析手动调整连接顺序创建索引优化查询逻辑 六、总结 在 PostgreSQL 中&#xff0c;表连…