C++语法 | 相关知识点 | 可以通过点击 | 以下链接进行学习 | 一起加油! |
---|---|---|---|---|
命名空间 | 缺省参数与函数重载 | C++相关特性 | 类和对象-上篇 | 类和对象-中篇 |
类和对象-下篇 | 日期类 | C/C++内存管理 | 模板初阶 |
这篇文章将带大家深入探讨C++ STL中的string使用与理解。在接下来的几篇文章中,我们将介绍STL中相关知识点。掌握STL将使我们在使用C++编写代码时更加得心应手。
🌈个人主页:是店小二呀
🌈C语言笔记专栏:C语言笔记
🌈C++笔记专栏: C++笔记
🌈初阶数据结构笔记专栏: 初阶数据结构笔记
🌈Linux笔记专栏: Linux笔记
🌈喜欢的诗句:无人扶我青云志 我自踏雪至山巅
文章目录
- 前文
- 一、标准库中的string类
- 二、string类对象的常见构造
-
- [2.1 string()](#2.1 string())
- [2.2 string(const char* s)](#2.2 string(const char* s))
- [2.3 string(size_t,char c)](#2.3 string(size_t,char c))
- [2.4 string(const string& s)](#2.4 string(const string& s))
- [2.5 string(const string& str,size_t pos,size_t len = npos)](#2.5 string(const string& str,size_t pos,size_t len = npos))
- 三、string类对象的容量操作
-
- [3.1 Size与length](#3.1 Size与length)
-
- [3.1.1 关于size与length相关问题](#3.1.1 关于size与length相关问题)
- [3.2 capacity](#3.2 capacity)
-
- [3.2.1 capacity返回值比size大](#3.2.1 capacity返回值比size大)
- [3.2.2 capacity扩容机制](#3.2.2 capacity扩容机制)
- [3.3 empty](#3.3 empty)
- [3.4 clear](#3.4 clear)
- [3.5 shrink_to_fit](#3.5 shrink_to_fit)
- [3.6 reserve(重要)](#3.6 reserve(重要))
-
- [3.6.1 关于reserve与扩容问题](#3.6.1 关于reserve与扩容问题)
- [3.7 resize(重要)](#3.7 resize(重要))
-
- [3.7.1 resize改变字符串的实际长度有三种情况](#3.7.1 resize改变字符串的实际长度有三种情况)
- 四、string类对象的访问
-
- [4.1 operator[]](#4.1 operator[])
- 五、string类对象的遍历操作
- 六、迭代器(简单介绍)
-
- [6.1 迭代器概念](#6.1 迭代器概念)
- [6.2 反向迭代器(reverse_iterator)](#6.2 反向迭代器(reverse_iterator))
- 七、Modifiers(string类对象的修改操作)
-
- [7.1 push_back](#7.1 push_back)
- [7.2 append](#7.2 append)
- [7.3 operator+=(重要)](#7.3 operator+=(重要))
- [7.4 assign](#7.4 assign)
- [7.5 insert](#7.5 insert)
- [7.6 erase](#7.6 erase)
- [7.7 replace](#7.7 replace)
- [7.8 find](#7.8 find)
-
- [7.8.1 size_t find(const char* s, size t pos = 0) const](#7.8.1 size_t find(const char* s, size t pos = 0) const)
- [7.8.2 size t find(char c,size t pos =0) const](#7.8.2 size t find(char c,size t pos =0) const)
- [7.9 关于replace与find配合使用](#7.9 关于replace与find配合使用)
- [7.10 rfind](#7.10 rfind)
-
- [7.10.1 size_t rfind(const char* str, size_t pos = npos) const](#7.10.1 size_t rfind(const char* str, size_t pos = npos) const)
- [7.10.2 size_t rfind(const c, size_t pos = npos) const](#7.10.2 size_t rfind(const c, size_t pos = npos) const)
- [7.11 substr](#7.11 substr)
- [7.12 c_str](#7.12 c_str)
- 八、string类非成员函数
-
- [8.1 relational operator](#8.1 relational operator)
- [8.2 getline](#8.2 getline)
- 九、VS和GCC下string结构说明
-
- [9.1 VS下string结构](#9.1 VS下string结构)
- [9.2 GCC下string结构](#9.2 GCC下string结构)
- 十、选择String类的理由
前文
C语言中,字符串是以'\0'结尾的字符集合。C标准库提供了一系列关于字符串的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。至于C++选择了string类而不是C语言中的字符串库函数,在本章最后揭晓。
一、标准库中的string类
在使用string类过程中,必须包括#include头文件以及using namespace std
。string类对象支持直接使用cin和cout进行输入和输出。
- string表示字符串类,字符串表示字符序列的类
- 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作
- string在底层实际为basic_string模板类的别名,typedef basic_string<char,char_traits,allocator>string
- string类是使用char,即作为它的字符类型,使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数
- 不能操作多字节或者变长字符的序列
接下来接收string类的常用接口
二、string类对象的常见构造
(constructor)函数名称 | 功能说明 |
---|---|
string() (重点) | 构造空的string类对象,即空字符串 |
string(const char* s) (重点) | 用C-string来构造string类对象 |
string(size_t n, char c) | string类对象中包含n个字符c |
string(const string&s) (重点) | 拷贝构造函数 |
2.1 string()
cpp
string str;
功能 :构造空string类对象,其中不存在成员对象
2.2 string(const char* s)
cpp
int main()
{
//第一种写法,清晰明了
const char* s = "hello world";
string str1(s);//
//第二种写法,比较简洁,常使用
string str2("hello world");
return 0;
}
功能 :使用C-string构造string类对象。在非空字符串中,从s指向位置拷贝一份字符串。
2.3 string(size_t,char c)
cpp
int main()
{
string str1(5, 'x');
cout << str1 << endl;//xxxxx
return 0;
}
功能:string类对象初始化n个字符c。从C-string的n个连续字符拷贝填充string类对象。
2.4 string(const string& s)
cpp
int main()
{
string str1("hello world");
string str2(str1);//拷贝构造str1
return 0;
}
2.5 string(const string& str,size_t pos,size_t len = npos)
cpp
int main()
{
string str1("helloo world");
string str2(str1, 5, 6);
cout << str2 << endl;
return 0;
}
功能 :从str中pos指向位置先后拷贝len长度字符。出现两种结果:拷贝到str最后一个字符或没有达到最后一个字符完成拷贝。
说明:第三个参数len类型为size_t,而缺省值npos == -1导致了npos为最大值128(涉及到编码那块)。对于当没有明确len数值,默认是从pos位置拷贝字符串到最后一个字符。
三、string类对象的容量操作
函数名称 | 功能说明 |
---|---|
size(重点) | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty (重点) | 检测字符串释放为空串,是返回true,否则返回false |
clear (重点) | 清空有效字符 |
reserve (重点) | 为字符串预留空间 |
resize (重点) | 将有效字符的个数该成n个,多出的空间用字符c填充 |
3.1 Size与length
cpp
int main()
{
string str1("hello world");
cout << str1.size() << endl;//11
cout << str1.length() << endl;//11
return 0;
}
功能:返回字符串有效字符长度。
3.1.1 关于size与length相关问题
- 至于出现两个功能类似接口的原因:
由于当时string只考虑字符串,同时使用length表示字符串长度是最合理的。但是这样没有考虑到其他类型,导致具有局限性,在树形结构等数据结构情况中不太适合length表示元素大小,STL添加size表示元素大小。length合理,size统一更规范。
- 为什么不删除length,只保留size呢?或者在string容器中只存在length表示大小呢?
在语言中库,一般遵守向前兼容,只错不能改。如果修改会导致之前代码就编译失败,对此不能删除length。其他容器都有size,就你string容器没有,是不是有点不太合适呀。
3.2 capacity
ccpp
int main()
{
string str1("hello world");
cout << str1.size() << endl;
cout << str1.capacity() << endl;
return 0;
}
输出结果:11 15
功能:返回空间总大小,一般情况下capacity返回大小中不包含'\0'
3.2.1 capacity返回值比size大
在C++中,std::string底层属于动态数组,数组大小是不固定,根据实际需要进行调正。由于经常性出现频繁插入字符的清空,只存在size情况下,会导致频繁地向系统申请空间,性能降低。
capacity可以有效地解决这问题,直接申请大于size空间大小,避免在每次追加字符中重新分配内存,直接使用capacity空间,减少向系统申请内存次数,提高性能。
3.2.2 capacity扩容机制
在C++中,std::string类中向字符串添加字符。如果出现容量不足去容纳新字符,会自动扩容(不需要手动扩容)。扩容的逻辑通常按照某种策略增加容量,具体实现会跟编译器和指标因子的不同有所差异。虽然string扩容机制没有明确的规定细节,但是不会影响功能。
- VS:扩容机制是第一次扩容到原来空间的两倍左右,之后则扩容当前空间的1.5倍
- GCC:扩容机制是以当前空间的两倍
3.3 empty
cpp
int main()
{
string str1;
if (str1.empty())//判断释放为空
cout << "为空" << endl;
else
cout << "非空" << endl;
return 0;
}
功能:检测字符串是否释放为空,是空返回true,否则返回false
3.4 clear
cpp
int main()
{
string str1("hello world");
cout << str1.size() << endl;
cout << str1.capacity() << endl;
str1.clear();//清空有效字符
cout << str1.size() << endl;
cout << str1.capacity() << endl;
return 0;
}
功能:清空string有效字符资源,不改变底层空间大小。影响有效元素size,不会影响空间容量大小capacity
3.5 shrink_to_fit
cpp
int main()
{
string str("hello world");
cout << str.size() << endl;
cout << str.capacity() << endl;
cout << endl;
str.resize(100);
cout << str.size() << endl;
cout << str.capacity() << endl;
cout << endl;
str.shrink_to_fit();
cout << str.size() << endl;
cout << str.capacity() << endl;
cout << endl;
return 0;
}
功能:向系统请求字符串缩容到适合大小,但是该函数对于字符串的长度和内容是没有影响的
如果使用shrink_to_fit后,容量没有发生改变,可能字符串对象可能已经使用内存管理策略去避免频繁的内存分配和释放。
3.6 reserve(重要)
cpp
int main()
{
string str1;
cout << str1.capacity() << endl;//15
str1.reserve(100);
cout << str1.capacity() << endl;//111
string str2(10, 'x');
cout << str2.capacity() << endl;//10
str2.reserve();
cout << str2.capacity() << endl;//10
return 0;
}
功能 :向系统申请预留空间,属于手动扩容
3.6.1 关于reserve与扩容问题
- 编译器会根据capacity容量自动扩容,那么为什么还需要reserve实现手段扩容呢?
- 理由:扩容是需要付出代价的,如果是异地扩容,付出代价更大,需要进行空间开辟和数据拷贝。
- 如果事先知道所需要的空间大小,使用reverse开辟足够使用的空间,减少频繁对内存的重分配,就算后期出现空间不足,也有自动扩容的机制,不需要担心大小是固定的。虽然自动扩容可以解决容量不足的情况,但是手段扩容可以减少频繁自动扩容的代价,属于一种优化手段。
- reverse要求100个字节空间,但却开辟了111个字节空间呢?
- 理由:在不同编译器下机制是不同的,但是确保了至少满足所需空间。有些编译器开辟多个空间,是对reserve开辟的空间进行了二次开辟,可以灵活调用内存空间分配,在后继需要小空间,避免扩容。
- reserve参数部分小于当前空间大小,提出申请空间请求,但是空间大小并没有发生改变
- 理由:reserve进行扩容必须参数部分比当前空间大,才会改变string的底层空间总大小,否则就是无效扩容。
3.7 resize(重要)
功能:改变字符串的实际长度
3.7.1 resize改变字符串的实际长度有三种情况
第一种:字符串变短(n>size)
cpp
int main()
{
string str1("hello world");//长度为11
cout << str1.size() << endl;//11
cout << str1.capacity() << endl;//15
str1.resize(2);
cout << str1 << endl;
cout << str1.size() << endl;//2
cout << str1.capacity() << endl;//15
return 0;
}
第二种:字符串在容量内变长(capacity>=n>size)
cpp
int main()
{
string str1("hello world");//长度为11
cout << str1.size() << endl;//11
cout << str1.capacity() << endl;//15
str1.resize(13);
cout << str1 << endl;
cout << str1.size() << endl;//13
cout << str1.capacity() << endl;//15
return 0;
}
如果需要保留字符串前几个字符(不包括'\0'),可以使用这个接口。
第三种:字符串修改长度超出容量(n>capcity)
cpp
int main()
{
string str1("hello world");//长度为11
cout << str1.size() << endl;//11
cout << str1.capacity() << endl;//15
str1.resize(50);
cout << str1 << endl;
cout << str1.size() << endl;//50
cout << str1.capacity() << endl;//63
return 0;
}
当resize修改长度超过capacity,capacity会进行自动扩容。至于最后capacity的值为什么不是50,在reserve中解释了不同编译器扩容机制是不同的。
resize有两个函数重载:resize(size_t n)与resize(size_t n,char c),功能都是将字符串中有效字符个数改变到n个。
不同点:当字符个数增多时
- resize(n):用'\0'来填充都出的元素空间
- resize(size_t n,char c):用字符c来填充多出的元素空间
虽然resize的功能很丰富,但是reserve比较多使用,可以提前开好空间,避免频繁扩容,提高了性能。
四、string类对象的访问
关于string类对象的访问,关键掌握operator[]就行。剩下三个不如operator[]好用,其中front、back是为了规范性才实现的。
4.1 operator[]
CPP
int main()
{
string str1("hello world");
for (int i=0;i<str1.size();i++)
{
//cout << str1.operator[](i) << endl;
cout << str1[i] << endl;
}
const string str2("hello world");
for (int i = 0; i < str2.size(); i++)
{
//str2[i]++; const修饰的话,没有修改的权限
cout << str2[i] << endl;
}
return 0;
}
五、string类对象的遍历操作
函数名称 | 功能说明 |
---|---|
operator[] (重 点) | 返回pos位置的字符,const string类对象调用 |
begin+ end | begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭 代器 |
rbegin + rend | begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭 代器 |
范围for | C++11支持更简洁的范围for的新遍历方式 |
string类对象三种遍历方式:
- for+[]
- 范围for(本质还是迭代器)
- 迭代器(begin(),end())
这个三种遍历方式不仅可以遍历string对象,还能遍历修改string中的字符
注意:在string遍历时使用最多的是for+下标方括号[]或者范围for(C++11后才支持)。begin()+end()大多数使用在需要使用STL提供的算法操作string时,比如:采用reverse逆置string。
第一种:for+下标方括号[]
cpp
int main()
{
string str1("hello world");
int sz = str1.size();
for (int i = 0; i < sz; i++)
{
//cout<<str1.operator[](i)<<endl;
cout << str1[i] << endl;
}
return 0;
}
第二种:范围for
cpp
int main()
{
string str1("hello world");
int sz = str1.size();
for (auto ch : str1)//从str1读取字符给变量ch,auto会自动识别类型
{
cout << ch << endl;
}
return 0;
}
接下来单独介绍下迭代器
六、迭代器(简单介绍)
6.1 迭代器概念
迭代器(Iterator)是一种用于遍历容器(如列表、字典、集合等)元素的对象。它提供了一种统一的访问容器内部元素的方式,而不必暴露容器的具体实现细节。迭代器通常用于循环结构中,让程序员能够逐个访问容器中的元素。
cpp
int main()
{
string str1("hello world");
string::iterator it = str1.begin();
while (it != str1.end())
{
cout << *it << endl;
it++;
}
return 0;
}
虽然推荐使用上面两种方式,但是迭代器才是主流 。对于链表、树等数据结构,迭代器不在乎底层实现,是通用的遍历容器。迭代器是一种像指针的东西,他可以是指针也可以不是指针,具体还是看不同编译器的底层实现,迭代器有两种类型分别:可读可修改,可读不可修改
在string中定义迭代器:
string::iterator it = str1.begin();
string::iterator it = str1.end();
简单说明:
begin()返回指向第一个位置的迭代器或指针,可以用于访问第一个位置元素,而end()返回指向最后一个位置之后的迭代器,它指向的是标记字符串的结尾。
可以使用容器类型的迭代器类型来声明变量:std::vector<int>
容器,begin()和end()
返回的是str::vector<int>::iterator
类型的迭代器,可以将其存储相应的变量中。
总之,存储迭代器的变量类型应该与容器的迭代器类型相匹配,以确保类型的一致性,避免编译器报错或者意外行为
6.2 反向迭代器(reverse_iterator)
反向迭代器定义:string::reverse_iterator
cpp
int main()
{
string str1("hello world");
string::reverse_iterator rit = str1.rbegin();
while (rit != str1.rend())
{
cout << *rit << "";
++rit;
}
cout << endl;
return 0;
}
说明:rbegin()返回逆向迭代器位置,由于本来就是倒置的,++就是向前遍历。实际上很少使用场景,正向迭代器也可以满足倒着遍历的需求。
七、Modifiers(string类对象的修改操作)
函数名称 | 功能说明 |
---|---|
push_back | 在字符串后尾插字符c |
append | 在字符串后追加一个字符串 |
operator+= (重点) | 在字符串后追加字符串str |
c_str(重点) | 返回C格式字符串 |
find + npos(重点) | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
rfind | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好,减少扩容次数,提高性能
7.1 push_back
cpp
int main()
{
string str1;//定义一个string类对象
for (int i = 0; i < 10; i++)
{
str1.push_back('c');
}
cout << str1 << endl;
return 0;
}
功能:将一个字符尾插到字符串中
疑问:push_back也不常用,既然支持一个字符尾插,为什么不顺便支持字符串。需要实现个支持字符串的接口,两个接口放在一起显得冗余
7.2 append
cpp
int main()
{
string str1;
str1.append("hello world");
cout << str1 << endl;
return 0;
}
功能:在字符串后追加一个字符串
对于append有很多个函数重载,这里只需要记住经常使用的就行了,其他了解下即可,但是无论是push_back,还是append,我们都不喜欢使用的,比较喜欢使用operator+=
7.3 operator+=(重要)
cpp
int main()
{
string str1,str2;
str1 += 'c';
cout << str1 << endl;
str2 += "hello world";
cout << str2 << endl;
str2 += str1;
cout << str2 << endl;//输出结果:hello worldc
return 0;
}
注意 :虽然str1.push_back('c')、str1.append(1,'c')、str1+='c'
三种的实现逻辑差不多,但是一般情况string类使用operator+=操作比较多,在于+=操作不仅可以连接单个字符,还可以连接字符串
7.4 assign
cpp
int main()
{
string str1("hello world");
str1.assign("xxx");
cout << str1 << endl;//xxx
return 0;
}
功能 :将空间中数据清空再添加所需内容,就是赋值的意思。如果出现空间不足问题,会自动扩容满足当前空间需求。
7.5 insert
cpp
int main()
{
string str1("hello world");
str1.insert(2, "xxx");
cout << str1 << endl;//hexxxllo world
return 0;
}
功能:从某个位置开始插入字符
7.6 erase
cpp
int main()
{
string str1("hello world");
str1.erase(0, 3);
cout << str1 << endl;
str1.erase();
cout << str1 << endl;
return 0;
}
功能:从某个位置开始删除len个字符。如果需删除字符超过size,则有多少删多少
如果没有给具体需要删除几个字符,采用缺省值npos,默认全部删除。留下一行空格表示,当前空间没有释放。
7.7 replace
cpp
int main()
{
string str1("hello world");
str1.replace(5, 1, "%");
cout << str1 << endl;//hello%world
return 0;
}
功能 :在字符串中某个区间位置的字符进行字符替换
小结:对于insert、erase、replace来说,底层逻辑是挪动数据,时间复杂度很高,效率很低,能不使用就不使用,建议多使用operator+=。
7.8 find
返回值:如果找到相对应的字符后,find会返回该字符所在的索引位置(从0开始的下标索引位置),如果没有匹配成功,find则会返回npos(-1);
7.8.1 size_t find(const char* s, size t pos = 0) const
cpp
size_t find(const char* s, size t pos = 0) const;
int main()
{
string str1("file.cpp");
size_t pos = str1.find('.');
cout << pos << endl;
return 0;
}
功能:find从pos开始位置,查找字符串中所需字符所在位置
7.8.2 size t find(char c,size t pos =0) const
cpp
int main()
{
string str1("https://cplusplus.com/reference/string/string/find/");
size_t pos = str1.find("://", 2);
cout << pos << endl;
return 0;
}
功能 :find从pos开始位置,查找字符串中所需字符串位置,并返回开头字符的下标
7.9 关于replace与find配合使用
(面试题 01.03. URL化 - 力扣(LeetCode)
cpp
int main()
{
string str1("I am a cold boy");
size_t pos = str1.find(' ');
while (pos != string::npos)//直到空格替换完
{
str1.replace(pos, 1, "%20");
pos = str1.find(' ');
}
cout << str1 << endl;
return 0;
7.10 rfind
返回值 :如果找到相对应的字符或字符串后,rfind会返回该字符或者返回该字符串最后一个字符所在的索引位置;如果没有匹配成功,rfind则会返回npos(-1);
7.10.1 size_t rfind(const char* str, size_t pos = npos) const
cpp
int main()
{
string str1("https://cplusplus.com/reference/string/string/rfind/");
size_t pos = str1.rfind('/');
cout << pos << endl;
return 0;
}
功能 :rfind从pos开始位置,向前查找字符串中所需字符所在位置。如果没有指定该pos值,采用缺省值,从字符串最后一位开始
7.10.2 size_t rfind(const c, size_t pos = npos) const
cpp
int main()
{
string str1("https://cplusplus.com/reference/string/string/find/");
size_t pos = str1.rfind("com");
cout << pos << endl;
return 0;
}
功能 :rfind从pos开始位置,向前查找字符串中所需字符串所在位置,并返回字符串末尾字符索引位置;如果没有指定该pos值,采用缺省值,从字符串最后一位开始
7.11 substr
cpp
int main()
{
string file("string.cpp");
size_t pos = file.rfind('.');
string suffix(file.substr(pos, file.size() - pos));
cout << suffix << endl;
return 0;
}
功能:在str中pos位置开始,截取n个字符,然后将其返回。
如果没有给需要截取的字符长度,默认从pos位置截取到字符串末尾位置。通常遵循左闭右开的原则。这意味着它返回的子字符串包括起始索引,但不包括结束索引位置。
7.12 c_str
cpp
int main()
{
string str1("hello world");
char* p = new char[str1.size() + 1];
std::strcpy(p, str1.c_str());
return 0;
}
功能:返回C格式字符串(包括'\0')
八、string类非成员函数
**接下来介绍以下函数不属于string类中函数,而是属于全局函数。如果需要使用需要使用对应的头文件。**这里只简单介绍两个函数
函数 | 功能说明 |
---|---|
operator+ | 尽量少用,因为传值返回,导致深拷贝效率低 |
operator>> (重点) | 输入运算符重载 |
operator<< (重点) | 输出运算符重载 |
getline (重点) | 获取一行字符串 |
relational operators (重点) | 大小比较 |
8.1 relational operator
cpp
#include <string>
int main()
{
string str1("abc");
string str2("acb");
if (str1 == str2) cout << "str1 and str2 are equal" << endl;
if (str1 != str2) cout << "str1 and str2 are not equal" << endl;
//.................................
return 0;
}
功能:就是大小的比较,重载compare来使用的
8.2 getline
cpp
#include <string>
int main()
{
string name("li hua");
getline(cin,name);
cout << name << endl;
}
getline功能:
-
从流中得到字符串,解决了读取字符串遇到空格或者换行符终止问题,当然也可以使用getchar完成.
-
C++ 取不到空格和换行 ,默认为换行和空格是一个分隔符,自动忽略
-
在C++中,尽管可以使用C语言中的标准库函数如
getchar
和getc
,但是在纯C++编程中,建议使用C++标准库提供的功能来进行输入操作。
九、VS和GCC下string结构说明
下述结构是32位平台下进行验证,32位平台下指针占4个字节
9.1 VS下string结构
string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来:
当字符串长度小于16时,使用内部固定的字符数组来存放
当字符串长度大于等于16时,从堆上开辟空间
cpp
union _Bxty
{ // storage for small buffer or pointer to larger one
value_type _Buf[_BUF_SIZE];
pointer _Ptr;
char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;
由于大多数情况下字符串的长度都小于16。当创建好string对象,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。(buff数组优化)
其次还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量。最后还有一个指针做一些其他事情
故总共占16+4+4+4=28个字节。
9.2 GCC下string结构
G++下的,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:
-
空间大小
-
字符串有效长度
-
引用计数
cpp
struct _Rep_base
{
size_type _M_length;
size_type _M_capacity;
_Atomic_word _M_refcount;
};
- 指向堆空间的指针,用来存放字符串
小内存不用去堆上开,栈开空间比堆块,是在编译时就算好了,成本低。
十、选择String类的理由
在C++编程中,选择 std::string
而不是 C 风格的字符串(C-string,即使用字符数组或指针表示的字符串,如 char*
)通常是一个更好的选择。以下是几个关键原因:
- 安全性:
std::string
:自动管理内存,避免了手动分配和释放内存的风险,减少了内存泄漏和缓冲区溢出的可能性。- -C-string:需要手动管理内存,容易出现缓冲区溢出和内存泄漏问题,尤其是在处理长字符串或动态字符串时。
- 简洁性和易用性:
std::string
:提供了丰富的成员函数,如拼接、查找、替换、截取等操作,这些操作使用方便且更符合面向对象的编程风格。- -C-string :需要使用标准库函数(如
strcpy
、strcat
、strlen
等)进行操作,语法较为繁琐,不易于阅读和维护。
- 动态扩展:
std::string
:自动处理字符串的大小,支持动态扩展,无需担心容量不足问题。- -C-string:需要预先定义长度,动态扩展时需要手动重新分配内存,这增加了代码的复杂性和出错的可能性。
- 兼容性与集成:
std::string
:与C++标准库和STL容器无缝集成,能够更好地与其他C++标准库组件协同工作。- C-string :虽然与C标准库函数兼容,但与C++标准库组件的结合不如
std::string
方便。
- 异常处理:
std::string
:如果操作失败,std::string
通常会抛出异常,可以通过捕获异常来处理错误情况,代码更健壮。C-string:C-string操作一般通过返回值或设置错误码来处理失败情况,需要手动检查,容易忽略错误处理。
- 性能优化:
std::string
:在现代编译器中,std::string
的实现经过了大量优化,能有效减少不必要的内存拷贝,提高性能。- C-string:需要开发者手动进行性能优化,如内存管理、字符串拼接等,容易出错且不易维护。
总结
使用 std::string
可以显著提高代码的安全性、可读性和可维护性,同时减少了手动内存管理带来的复杂性和风险。在现代 C++ 编程中,std::string
已成为处理字符串的首选工具,除非在特定情况下(如需要与 C 代码库兼容)才会选择使用 C-string。
以上就是本篇文章的所有内容,在此感谢大家的观看!这里是店小二呀C++笔记,希望对你在学习C++语言旅途中有所帮助!