3-15 第一次回顾修订 错别字更改 重点内容标注 语言逻辑改善
目录
2.begin()与end() 及 rbegin()与rend() 返回"迭代器"(类似于指针)。
4.7.2.g++下string的结构g++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:
1.STL简介
什么是STL
STL(standard template library)标准模板库:是C++标准模板库的重要组成部分,不仅是一个可以复用的组件库,而且是一个保罗万象的数据结构和算法的软件框架。
2.auto关键字
前言:作类型替代符,auto声明的变量类型由编译器在编译时推导而得。
1.auto与auto*没有区别都可以声明指针变量,auto&用于声明引用。
2.在使用auto于同一行连续声明多个变量时变量类型一定要相同。
3.auto不能作为函数参数,不能用于声明数组,但是可以作为返回值(作返回值时要谨慎使用)。(可能导致逻辑混乱可读性低等问题)。
3.范围for
1.构成 : " 操作变量 : 被迭代范围 "。
2.自动迭代无需手动控制。
3.仅支持拥有迭代器的容器操作。
int arr[] = { 12,321,43,24,34,12 };
for (auto a : arr)
{
cout << a << " ";
}
3.范围for可以用作数组和容器对象上进行遍历。
4.范围for的底层很简单,容器遍历实际就是替换为相应的迭代器,可从汇编层看到。
4.string
4.1构造函数
| 构造函数格式 | 优先级 | 功能说明 |
|---|---|---|
string() | 🌟重点 | 构造空的 string 类对象,即空字符串 |
string(const char* s) | 🌟重点 | 使用 C 语言风格字符串(C-string)构造 string 类对象 |
string(size_t n, char c) | 普通 | 创建包含n 个重复字符 c的 string 类对象 |
string(const string& s) | 🌟重点 | 拷贝构造函数,用已存在的 string 对象创建新的副本 |
4.2string类操作
| 函数名称 | 优先级 | 功能说明 |
|---|---|---|
size() | 🌟重点 | 返回字符串有效字符长度(不包含结束符) |
capacity() | — | 返回字符串当前分配的总空间大小 |
empty() | 🌟重点 | 判断字符串是否为空,空返回 true,非空返回 false |
clear() | 🌟重点 | 清空字符串有效字符(变为空串,空间不一定释放),底层空间容量保持。 |
reserve() | 🌟重点 | 提前预留空间,只改变容量,不改变有效字符个数。※完全不往里面放任何有意义的东西 |
resize() | 🌟重点 | 修改有效字符个数,多出的位置用指定字符填充 |
4.2.1注意事项
1.size()与length()函数的底层实现逻辑一致,length是早期函数。size的引入是为了与其他容器兼容。 (以后一般使用size就行)
2.clear清除有效字符但底层空间大小仍然保持。
3.不同的是当字符个数增多时:resize(n)用'\0'来填充多出的元素空间,resize(size_t n, char c)用指定字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
4.4string类对象的访问及遍历操作
| 函数名称 | 优先级 | 功能说明 |
|---|---|---|
operator[] | 🌟重点 | 重载 [],返回 pos 位置的字符,支持普通 / const 对象调用。 |
begin() + end() | — | 获取正向迭代器:begin 指向首字符,end 指向最后一个字符的下一个位置 |
rbegin() + rend() | — | 获取反向迭代器:rbegin 指向尾字符,rend 指向首字符的前一个位置 |
1.重载[ ]运算符 可更改可访问。
//operator[]的string类重载
string s("chicken");
cout << s[0] << endl; 输出'c'
s[0] = 'j'; 更改为'j'
cout << s[0] << endl; 输出'j'
2.begin()与end() 及 rbegin()与rend() 返回"迭代器"(类似于指针)。
string s1("apple");
string::iterator itrt = s1.begin(); 定义迭代器
while (itrt != s1.end())
{
cout << *itrt << " ";
itrt++;
}
4.5string类对象的修改操作
| 函数名称 | 优先级 | 功能说明 |
|---|---|---|
push_back | — | 在字符串尾部插入一个字符 c |
append | — | 在字符串尾部追加一个字符串 |
operator+= | 🌟重点 | 在字符串尾部拼接字符串 / 字符,最常用 |
c_str | 🌟重点 | 返回 C 语言格式的字符串指针(const char*) |
find + npos | 🌟重点 | 从 pos 位置向后查找字符 / 字符串,找到返回下标,找不到返回 npos。(找一个!) |
rfind | — | 从 pos 位置向前查找字符 / 字符串 |
substr | — | 从 pos 位置开始,截取 n 个字符并返回 |
4.6string类非成员函数
| operator+ | 尽量少用因为传值拷贝,深拷贝。 |
| operator<< | 流插入运算符 输出运算符 |
| operator>> | 流提取运算符 输入运算符 |
| getline | 获取一行字符串 |
4.7string的结构(了解)
4.7.1VS2022下的结构
注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。
vs下string的结构
string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义
string中字符串的存储空间:
- 当字符串长度小于16时,使用内部固定的字符数组来存放。
- 当字符串长度大于等于16时,从堆上开辟空间。
union _Bxty
{
// storage for small buffer or pointer to larger one
value_type _Buf[_BUF_SIZE]; string过小时buffer数组暂存
pointer _Ptr;
char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;
这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。
其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量
最后:还有一个指针做一些其他事情。
故总共占16+4+4+4=28个字节
4.7.2.g++下string的结构
g++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:
- 空间总大小
- 字符串有效长度
- 引用计数
5.写时拷贝与引用计数(了解)
总结:
-
引用计数 是数据,记录共享人数。
-
写时拷贝 是策略,决定何时真复制(有人要修改时才复制)。
写时拷贝和引用计数经常放在一起讲,因为它们是一对搭档,核心目的是减少不必要的内存拷贝,提高效率。
尤其是在老的C++实现(比如某些旧版的标准库的std::string)中,这是一种常见的优化手段。虽然现代C++标准已经禁止了这种做法(因为多线程环境下有更好的方案),但这个思想对理解底层原理很有帮助。
5.1 引用计数(Reference Counting)
它是什么?
引用计数本质上是一个计数器,它记录着当前有多少个对象正在共享同一块内存资源。
工作逻辑:
-
创建:当你创建一个字符串对象
str1 = "hello"时,系统会分配一块内存来存放"hello",并设置一个引用计数,初始值为 1。 -
共享:当你用
str1去拷贝构造另一个字符串str2 = str1时,传统的做法是立即分配新内存并把内容复制过去。但有了引用计数,它会偷个懒:不分配新内存,也不复制字符。它只是让str2也指向str1的那块内存,然后把引用计数加 1。此时计数为 2。 -
再共享:如果再执行
str3 = str2,同样不复制,只让str3指向同一块内存,计数再加 1,变成 3。
这样做的好处是: 当字符串对象被频繁拷贝(比如传入函数、作为返回值)时,省去了反复分配和拷贝大量字符的时间,性能提升明显。
5.2 写时拷贝(Copy-on-Write,COW)
它是什么?
写时拷贝是一种策略,它回答的问题是:什么时候才真正进行拷贝?
核心原则: “读的时候大家共享,写的时候才各自独立。”
工作逻辑:
接上面的例子,现在 str1、str2、str3 都指向同一块内存,引用计数为 3。
-
读操作:如果程序只是读取内容(比如
cout << str1或char c = str2[2]),没有任何问题,大家继续共享,引用计数保持不变。 -
写操作:突然,程序试图修改其中一个字符串,比如
str2[1] = 'a'。-
str2想在自己那块内存上写字。但它发现,这块内存并不只有自己一个人在用(引用计数 > 1)。 -
为了避免修改影响到
str1和str3,str2会执行以下操作:-
重新分配:申请一块新的内存。
-
真正拷贝:把旧内存里的内容复制到新内存中。
-
修改:在新内存上执行修改操作(把第二个字符改成
'a')。 -
调整计数:把旧内存的引用计数减 1(此时变成 2),然后自己这块新内存的引用计数初始化为 1。
-
-
这个过程就是“写时拷贝”——只有在发生写入时,才进行真正的拷贝。如果一直没人写,就一直共享,节省了大量内存和CPU时间。

487

被折叠的 条评论
为什么被折叠?



