《Effective STL》读书笔记(二):vector和string

vector 和 string 优先于动态分配数组

当使用new动态分配内存时,我们需要关注以下内容

  1. 必须保证动态分配的内存会被delete,否则会造成资源泄露
  2. 必须确保使用了正确的delete形式。如果分配了单个对象,则必须使用delete;如果分配了数组,则需要使用delete[]。如果使用了不正确的delete形式,结果将是不确定的。可能会导致程序运行时崩溃,也有可能是资源泄露
  3. 必须确保只delete了一次。如果多次delete,结果同样是不确定的

vectorstring就消除了上述的负担,当元素加入到容器中时,它们的内存会增加;当vectorstring被析构时,它们的析构函数会自动析构容器内的元素。

许多string实现背后使用了引用计数技术,可以消除不必要的内存分配和不必要的字符拷贝,但是这种优化在多线程环境下可能会适得其反,所以如果认为string的引用计数实现会影响效率,那么可能有下面这几种选择

  • 看是否可以禁止引用计数
  • 寻找另外一个不使用引用计数的string实现
  • 考虑使用vector<char>来替代stringvector的实现不使用引用计数,不会发生隐藏的多线程性能问题。string的大多数功能可以通过算法库中的函数来替代。

使用reserve来避免不需要的内存分配

如果确切知道容器内最后会有多少元素的话,可以直接让容器预留合适的容量;如果不知道容器内最后有多少元素的话,可以先预留足够大的空间,然后当把所有数据都加入后,再去除多余的容量。

string实现的多样性

几乎每个string实现都包含以下信息

  • 字符串的大小,即包含字符的个数
  • 该字符串的最大容量
  • 字符串的值,即构成该字符串的字符

除此之外还可能包含

  • 它的分配器的一份拷贝

建立在引用计数基础上的string实现可能还包含

  • 对值的引用计数

不同的实现方式之间string的差别很大:

  • string的值可能被引用计数,也可能不会
  • string对象大小的范围可能是一个char*指针的大小的1倍到7倍
  • 创建一个新的字符串值可能需要零次、一次或两次动态分配内存
  • string对象可能共享,也可能不共享其大小和容量信息
  • string可能支持,也可能不支持针对单个对象的内存分配器
  • 不同的实现对字符内容的最小分配单位有不同策略

vector和string数据传给旧的API

要把vector传给使用数组的函数,只需要传入&v[0]即可,vector内部的元素布局保证和数组相同,所以这样的做法是正确的。可能出现问题的地方是如果vector此时的size为0,那么就可能产生不可预知的后果。所以在传参之前需要先判断以下vector.empty()

上面这种方法对string就无效了,因为:

  1. string中数据不一定存储在连续的内存空间中
  2. string的内部表示不一定是以空字符结尾的

所以通常我们使用c_str函数来获取可供C语言使用的字符串指针,即使长度为0也可以。

上面的方法对于要传入const指针的情况是没有问题的,但是如果在函数中要修改vector或者string的值就可能会出现问题。

对于string来说,c_str()返回的并不一定是字符串数据的内部表示,还可能是一个字符串数据的不可修改的拷贝。

对于vector来说,修改其中元素的值通常是没有问题的,但是不能试图修改元素的个数:这会导致vector内部的状态混乱,如果此时vector.size() == vector.capicity(),那么添加新的元素也会产生不可预知的后果。

如果想使用C API来初始化一个vector,那么可以利用vector和数组的内存布局兼容性,向API传入该vector中元素的存储区域

size_t fillArray(double* pArray, size_t arraySize);  // 返回已被写入的double数据的个数vector<double> vd(maxNumDoubles);		// 创建大小为maxNumDoubles的vector
vd.resize(fillArray(&vd[0], vd.size()));

这样的方法只对vector有效,因为只有vector才保证和数组有同样的内存布局。如果想要使用来自C API的数据初始化一个string,也可以使用其他方法做到。只需要让API把数据放到一个vector<char>中,然后再把数据从vector拷贝到相应的字符串中即可:

size_t fillString(char* pArray, size_t arraySize);vector<char> vc(maxNumChars);
size_t charsWritten = fillString(&vc[0], vc.size(0));  // 使用fillString向vc中写入数据
string s(vc.begin(), vc.begin() + charsWritten);  // 通过区间构造函数完成

按照这样的方法,也可以初始化其他容器

size_t fillArray(double *pArray, size_t arraySize);
vector<double> vd(maxNumDoubles);
vd.resize(fillArray(&vd[0], vd.size()));deque<double> d(vd.begin(), vd.end());
list<double> l(vd.begin(), vd.end());
set<double> s(vd.begin(), vd.end());

反过来,其他容器的内容也可以通过vector作为媒介传递到C API中。

void doSomethine(const int* pInts, size_t numInts);
set<int> intSet;vector<int> v(intSet.begin(), intSet.end());
if (!v.empty()) doSomethine(&v[0], v.size());

使用swap技巧来去除多余的容量

如果一个vector的容量很大,但是其中元素数量比较少,如果我们想把vector的容量缩减到合适的大小(这种容量的缩减通常被称为“shrink to fit”),我们可以通过下面的方法来实现这种缩减

class Contestant{...};
vector<Contestant> contestants;vector<Contestant>(contestants).swap(contestants);  // shrink to fit

表达式vector<Contestant>(contestants)通过拷贝构造函数创建了一个临时变量,这个临时变量的容量恰好是contestants的元素数量,然后再通过调用swap来交换临时变量和contestants的数据,交换之后临时变量得到了之前臃肿的容量,而contestants的容量大小刚刚好。在这句话结束之后,临时变量析构,多占用的内存就真正得到了释放。

对于字符串这样的操作也同样有效。

需要注意的是,这个技巧并不能完全保证缩减之后的容器一定没有冗余的容量,这是因为可能STL的具体实现会保留一些容量,这是无法避免的,但是这种技巧还是能保证使用后“在容器当前大小确定的情况下,使容量在该实现下变得最小”。

我们还可以通过这种技巧来清空一个容器:

string s;vector<Contestant>().swap(v);  // 和空容器进行swap
string().swap(s);

避免使用vector<bool>

如果一个对象是STL容器,那么一定下面的条件:如果c是包含对象T的容器,而且c支持operator[],那么下面的代码必须能够被编译

T *p = &c[0];  // 用operator[]返回值的地址初始化一个T的指针

但是这个条件在vector<bool>中是不成立的,像下面的代码无法编译通过

image-20230905181201929

image-20230905181012655

原因是vector<bool>是一个假容器,它并不真的存储bool,为了节省空间它存储的是bool的紧凑表示。在一个典型实现中,存储在vector中的bool仅占一个二进制位,所以一个8位的字节可以容纳8个bool。而指向一个二进制位的指针是被禁止的,所以vector<bool>::operator[]返回的是一个代理对象。

所以最好使用deque<bool>,它里面确实存储的是bool类型的数据,而且相比vector<bool>可以看到的省略只有reservecapacity。另外一个替代方法就是使用bitset,它不是STL容器,但是是C++标准的一部分,但是它的容量在创建时就指定了,没有办法动态调整大小或插入删除元素,因为它不是STL容器,所以它也不提供迭代器,但是提供了很多对位的集合有意义的成员函数。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python实现猎人猎物优化算法(HPO)优化卷积神经网络分类模型(CNN分类算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 猎人猎物优化搜索算法(Hunter–prey optimizer, HPO)是由Naruei& Keynia于2022年提出的一种最新的…

浅谈能源汽车下乡充电桩建设优化建议及解决方案

1.趋势分析 新能源汽车下乡已经成为提振汽车市场表现、推动汽车行业发展的重要措施。国家发改委日前也提出&#xff0c;汽车消费是支撑消费的“大头”&#xff0c;将加快推进充电桩和城市停车设施建设&#xff0c;大力推动新能源汽车下乡&#xff0c;鼓励汽车企业开发更适宜县…

将目标检测项目移植到linux上出现OSERROR

在windows上运行项目正常&#xff0c;但是在centos9上运行出现找到资源&#xff0c;第一次遇到这个问题&#xff0c;通过代码回找&#xff0c;一步一步发现&#xff0c;读取数据没问题&#xff0c;但是在预测的时候无法读取&#xff0c;查到的资料 说明显示字体问题&#xff0c…

十八、MySQL添加外键?

1、外键 外键是用来让两张表的数据之间建立联系&#xff0c;从而保证数据的一致性和完整性。 注意&#xff0c;父表被关联的字段类型&#xff0c;必须和子表被关联的字段类型一致。 2、实际操作 &#xff08;1&#xff09;初始化两张表格&#xff1a; 子表&#xff1a; 父…

【Apollo学习笔记】——规划模块TASK之PIECEWISE_JERK_NONLINEAR_SPEED_OPTIMIZER(二)

文章目录 TASK系列解析文章OptimizeByNLP1.get_nlp_info()定义问题规模2.get_bounds_info()定义约束边界约束3.get_starting_point()定义初值4.eval_f()求解目标函数5.eval_grad_f()求解梯度6.eval_g()求解约束函数7.eval_jac_g()求解约束雅可比矩阵8.eval_h()求解黑塞矩阵9. f…

刷题笔记18——数组查缺补漏、二分搜索变体

人就是这样的&#xff0c;想来想去&#xff0c;犹豫来犹豫去&#xff0c;觉得自己没有准备好&#xff0c;勇气没攒够&#xff0c;其实只要迈出去了那一步&#xff0c;就会发现其实所有的一切&#xff0c;早就准备好了。——巫哲Q《撒野》 528. 按权重随机选择 轮盘赌 class S…

synchronized 关键字

synchronized是Java中的关键字&#xff0c;它用于实现同步访问共享资源&#xff0c;可解决共享资源竞争问题&#xff0c;以确保多个线程之间共享资源访问的正确性。当一个方法或代码块被声明为synchronized时&#xff0c;只有一个线程可以执行该方法或代码块。其他尝试访问该方…

面试题 17.16. 按摩师

按摩师&#xff08;easy&#xff09; 题目链接: 面试题 17.16. 按摩师 题目描述: 一个有名的按摩师会收到源源不断的预约请求&#xff0c;每个预约都可以选择接或不接。在每次预约服务之间要有休息时间&#xff0c;因此她不能接受相邻的预约。给定一个预约请求序列&#xff0c…

冠达管理:股票退市整理期?

近些年来&#xff0c;随着我国股市的发展&#xff0c;股票市场的出资者逐渐增多。但在出资过程中&#xff0c;退市股票的问题也成为了备受重视的论题。那么&#xff0c;股票退市收拾期到底是什么&#xff1f;如何应对退市股票&#xff1f; 首要&#xff0c;什么是股票退市收拾…

【ES】笔记-Class类剖析

Class Class介绍与初体验ES5 通过构造函数实例化对象ES6 通过Class中的constructor实列化对象 Class 静态成员实例对象与函数对象的属性不相通实例对象与函数对象原型上的属性是相通的Class中对于static 标注的对象和方法不属于实列对象&#xff0c;属于类。 ES5构造函数继承Cl…

【LeetCode-中等题】46. 全排列

文章目录 组合并集问题汇总&#xff1a;题目方法一&#xff1a;递归回溯 组合并集问题汇总&#xff1a; 1、子集去重版本 2、组合去重版本 3、子集非去重版本 题目 这题中nums中的数各不相同&#xff0c;所以不需要去重&#xff1a; 而下面这题&#xff0c;nums中的数会存在重…

javaee spring 测试aop 切面

切面类 package com.test.advice;import org.aspectj.lang.ProceedingJoinPoint;//增强类 public class MyAdvice {//将这个增强方法切入到service层的add方法前public void before(){System.out.println("添加用户之前");}}目标类 package com.test.service;publi…