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.> 网上找的一种 类中类
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;
}
而这种资源的释放方式时在程序猿不知道的情况下进行的。
原理就是,程序在结束时,系统会自动析构所有的全局变量,实际上,系统也会析构所有类的静态成员变量,
就像这些静态变量时全局变量一样,我们知道 静态变量和全局变量在内存中,都是存储在静态存储区的,
所以在析构时 是同等对待的。
单例的模版:
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;