小白的一些面试题总结。

1. .h头文件中的ifdef/define/endif的作用


头文件中使用#ifndef #define #endif 能避免头文件的重定义

用于防止重复定义宏和重复包含头文件


2.重载(overload)和重写(override)的区别


overload 重载就是一个类里面可以用多个名称相同的方法,但是参数列表不一样

override 重写就是子类中的方法和父类中的某个方法完全一样,并且在子类创建

实例对象调用这个方法,调用子类中定义的方法。


3.多态的作用


多态是面向对象的重要特性,简单说“一个接口,多种实现”,就是同一种事物表现出不同状态

作用:

1.程序不必对每一个派生类编写功能调用,只需要对抽象基类进行处理,提高复用性

2.派生类的功能可以被基类的方法或饮用变量所调用,这叫向后兼容,可以提高可扩充性

和可维护性


4.new delete和malloc free的区别


相同点:都可以用于申请动态内存和释放内存

不同点

(1)malloc free 是c/c++语言的标准库函数 new/delete是c++运算符。对于非内部数据类的对象而言,光用maloc/free 无法满足动态对象的要求。对象在创建的同时要自动执行构造函数, 对象消亡之前要自动执行析构函数。由于malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加malloc/free。

(2)用法不同

 malloc原型 void * malloc(size_t size)

int *p = (int *)malloc(sizeof(int)*length)

在调用malloc时要显式地进行类型转换,

malloc函数本身并不识别要申请的内存是什么类型,它只关心内存总字节数


free原型  void free(void *memblock)

int *p = new int[length]

因为new内置了sizeof 类型转换和类型安全检查功能。对于非内部数据类型的对象而言,

new在创建动态对象的同时完成了初始化工作,如果对象有多个构造函数,那么new的语句

也可以有多种形式



5.#define DOUBLE(X)X+X,i = 5*(DOUBLE(5))  i是多少


define是宏 编译的时候要展开 i = 5 *5+5 =30


6.描述内存分配方式以及它们的区别


三种:

静态存储区分配:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在,如全局变量,static

在栈上创建:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束

时这些存储单元自动被释放。

在堆上分配:动态内存分配,程序在运行的时候用malloc或new申请任意多少的内存,程序员

自己负责free或delete释放内存


7.struct和class的区别


最本质的一个区别就是 默认的访问控制

struct是public的class是private的
你可以写如下的代码:
struct A
{
  char a;
};
struct B : A
{
  char b;
};

这个时候B是public继承A的。

如果都将上面的struct改成class,那么B是private继承A的。这就是默认的继承访问权限。 

所以我们在平时写类继承的时候,通常会这样写:
class B : public A

就是为了指明是public继承,而不是用默认的private继承。

 

当然,到底默认是public继承还是private继承取决于子类而不是基类

我的意思是,struct可以继承class,同样class也可以继承struct,那么默认的继承访问权限是看子类到底是用的struct还是class。如下:

 

struct A{};class B : A{}; //private继承
struct C : B{}; //public继承



比如:

struct A //定义一个struct
{
   char c1;
   int n2;
   double db3;
};
A a={'p', 7, 3.1415926}; //定义时直接赋值
 

struct 改成class

为什么就不能用了呢,因为class默认构造权限是private ,在类外就不能用{}赋值了


struct更适合看成是一个数据结构的实现体,class更适合看成一个对象的实现体


8.short a[10];

sizeof(a)的值是多少


short int : 2个字节 


9.列举STL中的集中容器并说明其特点


STL是C/C++开发中一个非常重要的模板,而其中定义的各种容器也是非常方便我们大家使用。下面,我们就浅谈某些常用的容器。这里我们不涉及容器的基本操作之类,只是要讨论一下各个容器其各自的特点。STL中的常用容器包括:顺序性容器(vector、deque、list)、关联容器(map、set)、容器适配器(queue、stac)。

1、顺序性容器

(1)vector

  vector是一种动态数组,在内存中具有连续的存储空间,支持快速随机访问。由于具有连续的存储空间,所以在插入和删除操作方面,效率比较慢。vector有多个构造函数,默认的构造函数是构造一个初始长度为0的内存空间,且分配的内存空间是以2的倍数动态增长的,即内存空间增长是按照20,21,22,23.....增长的,在push_back的过程中,若发现分配的内存空间不足,则重新分配一段连续的内存空间,其大小是现在连续空间的2倍,再将原先空间中的元素复制到新的空间中,性能消耗比较大,尤其是当元素是非内部数据时(非内部数据往往构造及拷贝构造函数相当复杂)。vector的另一个常见的问题就是clear操作。clear函数只是把vector的size清为零,但vector中的元素在内存中并没有消除,所以在使用vector的过程中会发现内存消耗会越来越多,导致内存泄露,现在经常用的方法是swap函数来进行解决:  vector<int> V;
    V.push_back(1); V.push_back(2);V.push_back(1); V.push_back(2);
    vector<int>().swap(V); 或者 V.swap(vector<int>());
  利用swap函数,和临时对象交换,使V对象的内存为临时对象的内存,而临时对象的内存为V对象的内存。交换以后,临时对象消失,释放内存。
    (2)deque

  deque和vector类似,支持快速随机访问。二者最大的区别在于,vector只能在末端插入数据,而deque支持双端插入数据。deque的内存空间分布是小片的连续,小片间用链表相连,实际上内部有一个map的指针。deque空间的重新分配要比vector快,重新分配空间后,原有的元素是不需要拷贝的。

(3)list

  list是一个双向链表,因此它的内存空间是可以不连续的,通过指针来进行数据的访问,这使list的随机存储变得非常低效,因此list没有提供[]操作符的重载。但list可以很好地支持任意地方的插入和删除,只需移动相应的指针即可。

(4)在实际使用时,如何选择这三个容器中哪一个,应根据你的需要而定,一般应遵循下面的原则:
    1) 如果你需要高效的随即存取,而不在乎插入和删除的效率,使用vector
    2) 如果你需要大量的插入和删除,而不关心随即存取,则应使用list
    3) 如果你需要随即存取,而且关心两端数据的插入和删除,则应使用deque

2、关联容器

(1)map

  map是一种关联容器,该容器用唯一的关键字来映射相应的值,即具有key-value功能。map内部自建一棵红黑树(一种自平衡二叉树),这棵树具有数据自动排序的功能,所以在map内部所有的数据都是有序的,以二叉树的形式进行组织。这是map的模板:
      template < class Key, class T, class Compare= less<Key>, class Allocator=allocator< pair<const Key,T> > > class map;
从模板中我们可以看出,再构造map时,是按照一定的顺序进行的。map的插入和删除效率比其他序列的容器高,因为对关联容器来说,不需要做内存的拷贝和移动,只是指针的移动。由于map的每个数据对应红黑树上的一个节点,这个节点在不保存你的数据时,是占用16个字节的,一个父节点指针,左右孩子指针,还有一个枚举值(标示红黑色),所以map的其中的一个缺点就是比较占用内存空间。

(2)set

  set也是一种关联性容器,它同map一样,底层使用红黑树实现,插入删除操作时仅仅移动指针即可,不涉及内存的移动和拷贝,所以效率比较高。set中的元素都是唯一的,而且默认情况下会对元素进行升序排列。所以在set中,不能直接改变元素值,因为那样会打乱原本正确的顺序,要改变元素值必须先删除旧元素,再插入新元素。不提供直接存取元素的任何操作函数,只能通过迭代器进行间接存取。set模板原型:
      template <class Key, class Compare=class<Key>, class Alloc=STL_DEFAULT_ALLOCATOR(Key) > class set;
  set支持集合的交(set_intersection)、差(set_difference)、并(set_union)及对称差(set_symmetric_difference) 等一些集合上的操作。

3、容器适配器

(1)queue

  queue是一个队列,实现先进先出功能,queue不是标准的STL容器,却以标准的STL容器为基础。queue是在deque的基础上封装的。之所以选择deque而不选择vector是因为deque在删除元素的时候释放空间,同时在重新申请空间的时候无需拷贝所有元素。其模板为:
    template < TYPENAME _Sequence="deque<_TP" typeneam _Tp,> > class queue;

(2)stack

  stack是实现先进后出的功能,和queue一样,也是内部封装了deque,这也是为啥称为容器适配器的原因吧(纯属猜测)。自己不直接维护被控序列的模板类,而是它存储的容器对象来为它实现所有的功能。stack的源代码原理和实现方式均跟queue相同。




10.写一个单例模式的模版类


几种单例模式:

1>最常用的

#include <iostream>
using namespace std;
 
class Singleton
{
public:
    static Singleton *GetInstance()
    {
        if (m_Instance == NULL )
        {
            m_Instance = new Singleton ();
        }
        return m_Instance;
    }
 
    static void DestoryInstance()
    {
        if (m_Instance != NULL )
        {
            delete m_Instance;
            m_Instance = NULL ;
        }
    }
 
    // This is just a operation example
    int GetTest()
    {
        return m_Test;
    }
 
private:
    Singleton(){ m_Test = 10; }
    static Singleton *m_Instance;
    int m_Test;
};
 
Singleton *Singleton ::m_Instance = NULL;
 
int main(int argc , char *argv [])
{
    Singleton *singletonObj = Singleton ::GetInstance();
    cout<<singletonObj->GetTest()<<endl;
 
    Singleton ::DestoryInstance();
    return 0;
}



2>加锁的 需要头文件<mutex>   eg: std::mutex a ; a.lock()



#include <iostream>
using namespace std;
 
class Singleton
{
public:
    static Singleton *GetInstance()
    {
        if (m_Instance == NULL )
        {
            Lock(); // C++没有直接的Lock操作,请使用其它库的Lock,比如Boost,此处仅为了说明
            if (m_Instance == NULL )
            {
                m_Instance = new Singleton ();
            }
            UnLock(); // C++没有直接的Lock操作,请使用其它库的Lock,比如Boost,此处仅为了说明
        }
        return m_Instance;
    }
 
    static void DestoryInstance()
    {
        if (m_Instance != NULL )
        {
            delete m_Instance;
            m_Instance = NULL ;
        }
    }
 
    int GetTest()
    {
        return m_Test;
    }
 
private:
    Singleton(){ m_Test = 0; }
    static Singleton *m_Instance;
    int m_Test;
};
 
Singleton *Singleton ::m_Instance = NULL;
 
int main(int argc , char *argv [])
{
    Singleton *singletonObj = Singleton ::GetInstance();
    cout<<singletonObj->GetTest()<<endl;
    Singleton ::DestoryInstance();
 
    return 0;
}


3> 是把对象 当成静态的全局 来开辟空间。

因为静态初始化在程序开始时,由主线程以单线程的方式完成了初始化,所以静态初始化实例

保证了线程安全性。在性能要求比较高时 可以使用这种方式



#include <iostream>
using namespace std;
 
class Singleton
{
public:
    static Singleton *GetInstance()
    {
        return const_cast <Singleton *>(m_Instance);
    }
 
    static void DestoryInstance()
    {
        if (m_Instance != NULL )
        {
            delete m_Instance;
            m_Instance = NULL ;
        }
    }
 
    int GetTest()
    {
        return m_Test;
    }
 
private:
    Singleton(){ m_Test = 10; }
    static const Singleton *m_Instance;
    int m_Test;
};
 
const Singleton *Singleton ::m_Instance = new Singleton();
 
int main(int argc , char *argv [])
{
    Singleton *singletonObj = Singleton ::GetInstance();
    cout<<singletonObj->GetTest()<<endl;
    Singleton ::DestoryInstance();
}



4.>  网上找的一种  类中类

#include <iostream>
using namespace std;
 
class Singleton
{
public:
    static Singleton *GetInstance()
    {
        return m_Instance;
    }
 
    int GetTest()
    {
        return m_Test;
    }
 
private:
    Singleton(){ m_Test = 10; }
    static Singleton *m_Instance;
    int m_Test;
 
    // This is important
    class GC
    {
    public :
        ~GC()
        {
            // We can destory all the resouce here, eg:db connector, file handle and so on
            if (m_Instance != NULL )
            {
                cout<< "Here is the test" <<endl;
                delete m_Instance;
                m_Instance = NULL ;
            }
        }
    };
    static GC gc;
};
 
Singleton *Singleton ::m_Instance = new Singleton();
Singleton ::GC Singleton ::gc;
 
int main(int argc , char *argv [])
{
    Singleton *singletonObj = Singleton ::GetInstance();
    cout<<singletonObj->GetTest()<<endl;
 
    return 0;
}

在程序运行结束时,系统会调用singleton的静态成员gc的析构函数,该析构函数会进行资源的释放,

而这种资源的释放方式时在程序猿不知道的情况下进行的。

原理就是,程序在结束时,系统会自动析构所有的全局变量,实际上,系统也会析构所有类的静态成员变量,

就像这些静态变量时全局变量一样,我们知道 静态变量和全局变量在内存中,都是存储在静态存储区的,

所以在析构时 是同等对待的。




单例的模版:

template <typename T>
class Singleton
{
public:
template<typename... Args>
  static T* Instance(Args&&... args)
  {

        if(m_pInstance==nullptr)

            m_pInstance = new T(std::forward<Args>(args)...);

        return m_pInstance;

    }

  static T* GetInstance()
  {
    if (m_pInstance == nullptr)
      throw std::logic_error("the instance is not init, please initialize the instance first");

    return m_pInstance;
  }

static void DestroyInstance()
    {
        delete m_pInstance;
        m_pInstance = nullptr;
    }

private:
        Singleton(void);
        virtual ~Singleton(void);
        Singleton(const Singleton&);
        Singleton& operator = (const Singleton&);
private:
    static T* m_pInstance;
};

template <class T> T*  Singleton<T>::m_pInstance = nullptr;

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值