c++八股文整理(八)

1. 什么是trivial destructor

“trivial destructor”一般是指用户没有自定义析构函数,而由系统生成的,这种析构函数在《STL源码解析》中成为“无关痛痒”的析构函数。

用户自定义了析构函数,则称之为“non-trivial destructor”,这种析构函数如果申请了新的空间一定要显式的释放,否则会造成内存泄露

对于trivial destructor,如果每次都进行调用,显然对效率是一种伤害,如何进行判断呢?

《STL源码解析》中给出的说明是:

首先利用value_type()获取所指对象的型别,再利用__type_traits<T>判断该型别的析构函数是否trivial,若是(__true_type),则什么也不做,若为(__false_type),则去调用destory()函数

也就是说,在实际的应用当中,STL库提供了相关的判断方法**__type_traits**。除了trivial destructor,还有trivial construct、trivial copy construct等,如果能够对是否trivial进行区分,可以采用内存处理函数memcpy()、malloc()等更加高效的完成相关操作,提升效率。

2. 使用智能指针管理内存资源,RAII是怎么回事?

  1. RAII全称是“Resource Acquisition is Initialization”,直译过来是“资源获取即初始化”,也就是说在构造函数中申请分配资源,在析构函数中释放资源。因为C++的语言机制保证了,当一个对象创建的时候,自动调用构造函数,当对象超出作用域的时候会自动调用析构函数。所以,在RAII的指导下,我们应该使用类来管理资源,将资源和对象的生命周期绑定。
  2. 智能指针(std::shared_ptr和std::unique_ptr)即RAII最具代表的实现,使用智能指针,可以实现自动的内存管理,再也不需要担心忘记delete造成的内存泄漏。有了智能指针,代码中几乎不需要再出现delete了。

3. 迭代器:++it、it++哪个好,为什么

  1. 前置返回一个引用,后置返回一个对象
    // ++i实现代码为:
    int& operator++()
    {
    
      *this += 1;
      return *this;
    
    } 
    
  2. 前置不会产生临时对象,后置必须产生临时对象,临时对象会导致效率降低
    //i++实现代码为:                 
    int operator++(int)                 
    {
    int temp = *this;                   
    
       ++*this;                       
    
       return temp;                  
    } 
    
  3. i++是先赋值再加1 ;++i是先加1再赋值。 在for循环中 对于 i 来讲,两者结果没有区别。
    int i = 5;
    int x = i++; // x = 5, i = 6 (called postfix)
    int x = ++i; // x = 6, i = 6 (called prefix)
    
    for (int i = 0; i < 10; i++) {}
    
    for (int i = 0; i < 10; ++i) {}
    
    

    事实上,i++ 和 ++i 都会带来同样的结果;这些都是 i=i+1 的缩写形式 ?

4. STL中hashtable的实现?

重建表格:当元素个数 > hashtable 表格的大小时,要求重建表格,重建表格是要将每一个元素重新进行hash,然后再delete掉旧的hashtable的所有元素。

hashtable中的bucket所维护的list既不是list也不是slist,而是其自己定义的由hashtable_node数据结构组成的linked-list,而bucket聚合体本身使用vector进行存储。hashtable的迭代器只提供前进操作,不提供后退操作

5. 简单说一下traits技法

traits技法利用“内嵌型别“的编程技巧与编译器的template参数推导功能,增强C++未能提供的关于型别认证方面的能力。常用的有iterator_traits和type_traits。

iterator_traits

被称为迭代器特性萃取机,能够方便的让外界获取以下5种型别:

  • value_type:迭代器所指对象的型别
  • difference_type:两个迭代器之间的距离
  • pointer:迭代器所指向的型别
  • reference:迭代器所引用的型别
  • iterator_category:三两句说不清楚,建议看书

type_traits

关注的是型别特性,例如这个型别是否具备non-trivial defalt ctor(默认构造函数)、non-trivial copy ctor(拷贝构造函数)、non-trivial assignment operator(赋值运算符) 和non-trivial dtor(析构函数),如果答案是否定的,可以采取直接操作内存的方式提高效率,一般来说,type_traits支持以下5中类型的判断:

__type_traits<T>::has_trivial_default_constructor
__type_traits<T>::has_trivial_copy_constructor
__type_traits<T>::has_trivial_assignment_operator
__type_traits<T>::has_trivial_destructor
__type_traits<T>::is_POD_type

由于编译器只针对class object形式的参数进行参数推到,因此上式的返回结果不应该是个bool值,实际上使用的是一种空的结构体:

struct __true_type{};struct __false_type{};

这两个结构体没有任何成员,不会带来其他的负担,又能满足需求,可谓一举两得

6. Vector注意小点

  • 即使clear()可以清空所有元素,vector所占用的内存空间依然如故,无法保证内存的回收。
  • resize()函数只改变容器的元素数目,未改变容器大小。
  • erase()函数,只能删除内容,不能改变容量大小;
  • 使用reserve()预先分配一块内存后,在空间未满的情况下,不会引起重新分配,从而提升了效率。
  • 不同的编译器,vector有不同的扩容大小。在vs下是1.5倍,在GCC下是2倍;
  • 如果需要空间动态缩小,可以考虑使用deque。
  • 如果使用vector,可以用swap()来帮助你释放多余内存或者清空全部内存。
vector(Vec).swap(Vec); //将Vec中多余内存清除; 
vector().swap(Vec); //清空Vec的全部内存;

实例:

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

int main ()
{
    vector<int> vec (100,100);   // three ints with a value of 100
    vec.push_back(1);
    vec.push_back(2);
    cout <<"vec.size(): " << vec.size() << endl;
    cout <<"vec.capasity(): " << vec.capacity() << endl;

    vector<int>(vec).swap(vec); //清空vec中多余的空间,相当于vec.shrink_to_fit();

    cout <<"vec.size(): " << vec.size() << endl;
    cout <<"vec.capasity(): " << vec.capacity() << endl;

    vector<int>().swap(vec); //清空vec的全部空间

    cout <<"vec.size(): " << vec.size() << endl;
    cout <<"vec.capasity(): " << vec.capacity() << endl;

    return 0;
}
/*
    运行结果:
    vec.size(): 102
    vec.capasity(): 200
    vec.size(): 102
    vec.capasity(): 102
    vec.size(): 0
    vec.capasity(): 0
 */

7. STL的两级空间配置器

1、首先明白为什么需要二级空间配置器?

我们知道动态开辟内存时,要在堆上申请,但若是我们需要频繁的在堆开辟释放内存,则就会在堆上造成很多外部碎片,浪费了内存空间;

每次都要进行调用malloc、free函数等操作,使空间就会增加一些附加信息,降低了空间利用率;

随着外部碎片增多,内存分配器在找不到合适内存情况下需要合并空闲块,浪费了时间,大大降低了效率。

于是就设置了二级空间配置器,当开辟内存<=128bytes时,即视为开辟小块内存,则调用二级空间配置器。

关于STL中一级空间配置器和二级空间配置器的选择上,一般默认选择的为二级空间配置器。 如果大于128字节再转去一级配置器器。

一级配置器

GC4.9之后就没有第一级了,只有第二级.

一级空间配置器中重要的函数就是allocate、deallocate、reallocate 。 一级空间配置器是以malloc(),free(),realloc()等C函数执行实际的内存配置 。大致过程是:

1、直接allocate分配内存,其实就是malloc来分配内存,成功则直接返回,失败就调用处理函数

2、如果用户自定义了内存分配失败的处理函数就调用,没有的话就返回异常

3、如果自定义了处理函数就进行处理,完事再继续分配试试

二级配置器

即内存池的概念,参考c++八股文整理(六)_yuanbo_小菜鸡的博客-CSDN博客第四段

8. 容器内部删除一个元素

  1. 顺序容器(序列式容器,比如vector、deque):erase迭代器不仅使所指向被删除的迭代器失效,而且使被删元素之后的所有迭代器失效(list除外),所以不能使用erase(it++)的方式,但是erase的返回值是下一个有效迭代器;It = c.erase(it);
  2. 关联容器(关联式容器,比如map、set、multimap、multiset等):erase迭代器只是被删除元素的迭代器失效,但是返回值是void,所以要采用erase(it++)的方式删除迭代器;c.erase(it++);​​​​​​​

9. STL中list与queue之间的区别

​​​​​​​

 

 

deque迭代器iterator包含node、first、last、cur四个指针 

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值