1. 迭代器和指针有什么区别?有了指针干嘛还要迭代器?⭐⭐⭐⭐⭐

  2. 前置 ++i 与后置 i++ 的区别⭐⭐⭐⭐⭐

  3. 请你说说STL⭐⭐⭐

  4. vector如何正确删除重复元素⭐⭐⭐⭐⭐

  5. 迭代器删除的问题⭐⭐⭐⭐⭐

  6. 请你说说函数模板与模板函数⭐⭐⭐⭐⭐

  7. 请你说说智能指针,智能指针为什么不用手动释放内存了?⭐⭐⭐⭐⭐

  8. auto_ptr有什么样的问题⭐⭐⭐⭐⭐

  9. unique_ptr指针实现原理⭐⭐⭐⭐⭐

  10. shared_ptr实现原理,来手撕一下⭐⭐⭐⭐⭐

  11. shared_ptr会不会出现内存泄露?怎么解决?⭐⭐⭐⭐⭐

  12. 说一说cast类型转换⭐⭐⭐⭐⭐

  13. 说一说lambda⭐⭐⭐⭐

=========================================================================================================

  • 本专栏适合于C/C++已经入门的学生或人士,有一定的编程基础。
  • 本专栏适合于互联网C++软件开发、嵌入式软件求职的学生或人士。
  • 本专栏针对面试题答案进行了优化,尽量做到好记、言简意赅。这才是一份面试题总结的正确打开方式。这样才方便背诵
  • 针对于非科班同学,建议学习本人专刊文章《蒋豆芽的秋招打怪之旅》,该专刊文章对每一个知识点进行了详细解析。
  • 如专栏内容有错漏,欢迎在评论区指出或私聊我更改,一起学习,共同进步。
  • 相信大家都有着高尚的灵魂,请尊重我的知识产权,未经允许严禁各类机构和个人转载、传阅本专栏的内容。

=========================================================================================================

  1. 迭代器和指针有什么区别?有了指针干嘛还要迭代器?⭐⭐⭐⭐⭐

    迭代器不是指针,是类模板,表现的像指针。它只是模拟了指针的一些功能,通过重载了指针的一些操作符,如-->*++--等。

    迭代器封装了指针,是一个“可遍历STL容器内全部或部分元素”的对象,本质是封装了原生指针,是指针概念的一种提升,相当于智能指针。而迭代器的访问方式就是把不同集合类的访问逻辑抽象出来,使得不用暴露集合内部的结构而达到循环遍历集合的效果。这就是迭代器产生的原因

  2. 前置 ++i 与后置 i++ 的区别⭐⭐⭐⭐⭐

    1. 赋值顺序不同:++ i 是先加后赋值;i ++ 是先赋值后加;++i和i++都是分两步完成的。
    2. 效率不同:后置++执行速度比前置的慢。
    3. i++ 不能作为左值,而++i 可以
    4. 两者都不是原子操作。

    C语言是汇编层面的实现,后置++的汇编代码比前置++多了一行,那么执行就会多花一点时间。但是随着编译器的不断发展,这样的区别已经微乎其微了。

    但是迭代器前置 ++i 与后置 i++ 的效率就有区别了。后置++要多生成一个局部对象 tmp,这个对象有可能包含很多的成员,因此执行速度比前置的慢。在次数很多的循环中,++i和i++可能就会造成运行时间上可观的差别了。

  3. 请你说说STL⭐⭐⭐

    STL 是“Standard Template Library”的缩写,中文译为“标准模板库”。STL 是 C++ 标准库的一部分,不用单独安装。C++ 对模板(Template)支持得很好,STL 就是借助模板把常用的数据结构及其算法都实现了一遍,并且做到了数据结构和算法的分离。例如,vector 的底层为顺序表(数组),list 的底层为双向链表,deque 的底层为双端队列,set 的底层为红黑树,hash_set 的底层为哈希表。

    通常认为,STL 是由容器算法迭代器函数对象适配器内存分配器这 6 部分构成,其中后面 4 部分是为前 2 部分服务的

    STL的组成 含义
    容器 一些封装数据结构的模板类,例如 vector 向量容器、list 列表容器等。
    算法 STL 提供了非常多(大约 100 个)的数据结构算法,它们都被设计成一个个的模板函数,这些算法在 std 命名空间中定义,其中大部分算法都包含在头文件 中,少部分位于头文件 中。
    迭代器 在 C++ STL 中,对容器中数据的读和写,是通过迭代器完成的,迭代器就是容器和算法之间的桥梁。
    函数对象 如果一个类将 () 运算符重载为成员函数,这个类就称为函数对象类,这个类的对象就是函数对象(又称仿函数)。
    适配器 可以使一个类的接口(模板的参数)适配成用户指定的形式,从而让原本不能在一起工作的两个类工作在一起。值得一提的是,容器、迭代器和函数都有适配器。
    内存分配器 为容器类模板提供自定义的内存申请和释放功能,由于往往只有高级用户才有改变内存分配策略的需求,因此内存分配器对于一般用户来说,并不常用。
  4. vector如何正确删除重复元素⭐⭐⭐⭐⭐

    主要是要防止删除元素时,迭代器失效的问题,在使用erase时要返回下一个元素的迭代器。

    vector<int>::iterator iter=vec.begin();  
    for(; iter!=vec.end();){  
        if(*iter == 3)  
            iter = vec.erase(iter);  
        else  
            ++iter;  
    }  
    
  5. 迭代器删除的问题⭐⭐⭐⭐⭐

    vector主要是要防止删除元素时,迭代器失效的问题,在使用erase时要返回下一个元素的迭代器。

    而map,set则不一样,map,set的数据结构采用的红黑树,删除当前元素时,不会影响到下一个元素的迭代器,所以在调用erase之前,记录下一个元素的迭代器即可。

    而对于list来说,它的数据结构是链表,使用了不连续分配的内存,并且它的erase方法也会返回下一个有效的迭代器,因此两种方式都可采用。

  6. 请你说说函数模板与模板函数⭐⭐⭐⭐⭐

    函数模板的重点是模板。表示的是一个模板,专门用来生产函数。

    模板函数是函数模板的一个实例化。

  7. 请你说说智能指针,智能指针为什么不用手动释放内存了?⭐⭐⭐⭐⭐

    使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等。

    正是因为指针存在这样的问题,C++便引入了智能指针来更好的管理堆内存。智能指针是利用了一种叫做RAII(资源获取即初始化)的技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。

    因为智能指针就是一个,当超出了类的作用域时,类会自动调用析构函数,自动释放资源。这样程序员就不用再担心内存泄露的问题了。

    C++里面有四个指针:auto_ptr、unique_ptr、shared_ptr、weak_ptr,后面三个是C++11支持的,第一个被C++11弃用。

  8. auto_ptr有什么样的问题⭐⭐⭐⭐⭐

    看如下代码:

auto_ptr<string> p1 (new string ("I am jiang douya."));   
auto_ptr<string> p2;   
p2 = p1; //auto_ptr不会报错.  

auto指针存在的问题是,两个智能指针同时指向一块内存,就会两次释放同一块资源,存在潜在的内存崩溃问题!因此auto指针被C++11弃用。应该用unique指针替代auto指针。

  1. unique_ptr指针实现原理⭐⭐⭐⭐⭐

    unique指针规定一个智能指针独占一块内存资源。当两个智能指针同时指向一块内存,编译报错。

    我们只需要将拷贝构造函数和赋值拷贝构造函数申明为private或delete。不允许拷贝构造函数和赋值操作符

  2. shared_ptr实现原理,来手撕一下⭐⭐⭐⭐⭐

    实现原理:有一个引用计数的指针类型变量,专门用于引用计数,使用拷贝构造函数和赋值拷贝构造函数时,引用计数加1,当引用计数为0时,释放资源。

    #include <iostream>  
    #include <stdlib.h>  
    using namespace std;  
    	  
    template <typename T>  
    class mysharedPtr {  
    public:  
        mysharedPtr(T* p = NULL);  
        ~mysharedPtr();  
        mysharedPtr(const mysharedPtr<T>& other);  
        mysharedPtr<T>& operator=(const mysharedPtr<T>& other);  
    private:  
        T* m_ptr;  
        unsigned int* m_count;  
    };  
      
    template <typename T>  
    mysharedPtr<T>::mysharedPtr(T* p) {  
        m_ptr = p;  
        m_count = new unsigned int(0);  
        ++(*m_count);  
        cout << "Constructor is succeed!" << endl;  
    }  
      
    template <typename T>  
    mysharedPtr<T>::~mysharedPtr() {  
        --(*m_count);  
        if ((*m_count) == 0) {  
            delete[] m_ptr;  
            m_ptr = NULL;  
            delete[] m_count;  
            m_count = NULL;  
            cout << "Destructor is succeed!" << endl;  
        }  
    }  
    	  
    template <typename T>  
    mysharedPtr<T>::mysharedPtr(const mysharedPtr<T>& other) {  
        m_ptr = other.m_ptr;  
        m_count = other.m_count;  
        ++(*m_count);  
        cout << "Copy constructor is succeed!" << endl;  
    }  
      
    template <typename T>  
    mysharedPtr<T>& mysharedPtr<T>::operator=(const mysharedPtr<T>& other) {  
        // 《C++ primer》:“这个赋值操作符在减少左操作数的使用计数之前使other的使用计数加1,  
        // 从而防止自身赋值”而导致的提早释放内存  
        ++(*other.m_count);  
        --(*m_count);  
        // 将左操作数对象的使用计数减1,若该对象的使用计数减至0,则删除该对象  
        if ((*m_count) == 0) {  
            delete[] m_ptr;  
            m_ptr = NULL;  
            delete[] m_count;  
            m_count = NULL;  
            cout << "Left side object is deleted!" << endl;  
        }  
        m_ptr = other.m_ptr;  
        m_count = other.m_count;  
        cout << "Assignment operator overloaded is succeed!" << endl;  
        return *this;  
    }  
    
  3. shared_ptr会不会出现内存泄露?怎么解决?⭐⭐⭐⭐⭐

    会出现内存泄露问题。

    共享指针的循环引用计数问题:当两个类中相互定义shared_ptr成员变量,同时对象相互赋值时,就会产生循环引用计数问题,最后引用计数无法清零,资源得不到释放。

    可以使用weak_ptr,weak_ptr是弱引用,weak_ptr的构造和析构不会引起引用计数的增加或减少。我们可以将其中一个改为weak_ptr指针就可以了。比如我们将class B里shared_ptr换成weak_ptr。

  4. 说一说cast类型转换⭐⭐⭐⭐⭐

    C++为了将强制类型转换变得更加明确、控制强制转换的过程,主要将强制转换细化为四种cast转换方式:const_caststatic_castdynamic_castreinterpret_cast

    1. const_cast用于强制去掉不能被修改的常数特性,但需要特别注意的是const_cast不是用于去除变量的常量性,而是去除指向常数对象的指针或引用的常量性,其去除常量性的对象必须为指针或引用。
    2. static_cast用于将一种数据类型强制转换为另一种数据类型。什么都可以转,最常用。
    3. dynamic_cast只能用于含有虚函数的类转换,用于类向上和向下转换。dynamic_cast通过判断变量运行时类型和要转换的类型是否相同来判断是否能够进行向下转换。
    4. reinterpret_cast主要有三种强制转换用途:改变指针或引用的类型、将指针或引用转换为一个足够长度的整形、将整型转换为指针或引用类型。
  5. 说一说lambda⭐⭐⭐⭐

    在我们编程的过程中,我们常定义一些只会调用一次的函数,这样我们就还得老老实实写函数名、写参数,其实还挺麻烦的.

    但是C++ 11 引入Lambda 表达式用于定义并创建匿名的函数对象,就可以简化我们的编程工作了。

    Lambda 的语法形式如下:

    [函数对象参数] (操作符重载函数参数) mutable 或 exception 声明 -> 返回值类型 {函数体}  
    

##豆芽点评 特别是智能指针,太喜欢考了,一定要掌握。