string类、不同编译器下string类的差异、写时拷贝与引用计数

3-15 第一次回顾修订 错别字更改 重点内容标注 语言逻辑改善

目录

1.STL简介

2.auto关键字

3.范围for

4.string

4.1构造函数

4.2string类操作

4.2.1注意事项

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

1.重载[ ] 运算符可更改可访问。

2.begin()与end() 及 rbegin()与rend() 返回"迭代器"(类似于指针)。

4.5string类对象的修改操作

4.6string类非成员函数

4.7string的结构(了解)

4.7.1VS2022下的结构

4.7.2.g++下string的结构g++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:

5.写时拷贝与引用计数(了解)

5.1 引用计数(Reference Counting)

5.2 写时拷贝(Copy-on-Write,COW)

5.3 总结:


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)

它是什么?
引用计数本质上是一个计数器,它记录着当前有多少个对象正在共享同一块内存资源。

工作逻辑:

  1. 创建:当你创建一个字符串对象 str1 = "hello" 时,系统会分配一块内存来存放 "hello",并设置一个引用计数,初始值为 1

  2. 共享:当你用 str1 去拷贝构造另一个字符串 str2 = str1 时,传统的做法是立即分配新内存并把内容复制过去。但有了引用计数,它会偷个懒:不分配新内存,也不复制字符。它只是让 str2 也指向 str1 的那块内存,然后把引用计数加 1。此时计数为 2

  3. 再共享:如果再执行 str3 = str2,同样不复制,只让 str3 指向同一块内存,计数再加 1,变成 3。

这样做的好处是: 当字符串对象被频繁拷贝(比如传入函数、作为返回值)时,省去了反复分配和拷贝大量字符的时间,性能提升明显。

5.2 写时拷贝(Copy-on-Write,COW)

它是什么?
写时拷贝是一种策略,它回答的问题是:什么时候才真正进行拷贝

核心原则: “读的时候大家共享,写的时候才各自独立。”

工作逻辑:

接上面的例子,现在 str1str2str3 都指向同一块内存,引用计数为 3。

  1. 读操作:如果程序只是读取内容(比如 cout << str1 或 char c = str2[2]),没有任何问题,大家继续共享,引用计数保持不变。

  2. 写操作:突然,程序试图修改其中一个字符串,比如 str2[1] = 'a'

    • str2 想在自己那块内存上写字。但它发现,这块内存并不只有自己一个人在用(引用计数 > 1)。

    • 为了避免修改影响到 str1 和 str3str2 会执行以下操作:

      • 重新分配:申请一块新的内存。

      • 真正拷贝:把旧内存里的内容复制到新内存中。

      • 修改:在新内存上执行修改操作(把第二个字符改成 'a')。

      • 调整计数:把旧内存的引用计数减 1(此时变成 2),然后自己这块新内存的引用计数初始化为 1。

这个过程就是“写时拷贝”——只有在发生写入时,才进行真正的拷贝。如果一直没人写,就一直共享,节省了大量内存和CPU时间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值