剑指offer和网上有很多考察类的面试题,我觉得很具有代表性,在这里做一个总结:
简答题一:我们可以用static修饰一个类的成员函数,也可以用const修饰类的成员函数?
一。剑指offer面试题2:实现一个单例模式的类。
要求:设计一个类,我们只能生成该类的一个实例。
该题目要求是这个类只能生成一个实例,所以我们要想办法控制类的构造函数,因为生成实例的入口都是构造函数。
解法一:将构造函数设置为私有的,对于类的外部将是不可见的。并且将它设置为静态的,只有当条件满足时唯一的调用一次
<pre name="code" class="cpp"><span style="font-family:Microsoft YaHei;font-size:14px;">class Singleton
{
public:
static Singleton GetInstance()
{
if(instance==NULL)
{
instance=new Singleton();
}
return instance;
}
private:
static Singleton(){};
static Singleton instance;
}</span>
这种解法思路清晰且直观,但是这只适合单线程的模式下,如果多线程的情况下,会出现两个线程同时访问这个类,当遇到if语句时instance的值还为NULL,两个线程就都创建出一个实例,这就与题目要求不符合。
基于上面的解法一,我们可以得到可行解法二:
<pre name="code" class="cpp"><span style="font-family:Microsoft YaHei;font-size:14px;">class Singleton
{
public:</span>
<span style="font-family:Microsoft YaHei;font-size:14px;"> Singleton getInstance()</span>
{ if (instance == NULL) {lock(); if (instance == NULL) { instance = new Singleton(); } unlock(); } return instance;}
<span style="font-family:Microsoft YaHei;font-size:14px;"> private:
static Singleton(){};
static Singleton instance;
}</span>
这样只够极低的几率下,通过越过了if (instance == NULL)的线程才会有进入锁定临界区的可能性,这种几率还是比较低的,不会阻塞太多的线程,但为了防止一个线程进入临界区创建实例,另外的线程也进去临界区创建实例,又加上了一道防御if (instance == NULL),这样就确保不会重复创建了。二。剑指offer面试题48:不能被继承的类
要求:用C++设计一个不能被继承的类。
要想要实现一个不能继承的类在C#中是比较简单的,使用关键字sealed就行,所以在这里我们既要实现一个简易版的sealed关键字,是不是很牛的感觉,其实并不是很难得一件事。
解法一:要想要一个类不能继承,我们首先应该想到的是将类的构造函数设置为私有的,如果一个类的构造函数被设置为私有的,那么它的子类将会无法调用它的构造函数,这和不能继承是一样的效果。同题一一样,我们还想要得到这个类的一个实例并且释放它,这是作为一个类所应该具有的功能。我们可以通过定义公有的静态函数来创建和释放类的实例。
<span style="font-family:Microsoft YaHei;font-size:14px;">class A
{
public:
static A * Construct(int n)
{
A *pa = new A;
pa->num = n;
cout<<"num is:"<<pa->num<<endl;
return pa;
}
static void Destruct(A * pIntance)
{
delete pIntance;
pIntance = NULL;
}
private:
A(){}
~A(){}
private:
int num;
};
</span>
这种方法只可以创建堆上的对象,不可以构建栈上的对象。
这种方法相信大家都可以想到,但是为了打动安静的面试官,我们得拿出点料来才行。
解法二:虚拟继承+友元类
<span style="font-family:Microsoft YaHei;font-size:14px;">#include<iostream>
using namespace std;
template <typename T>
class Base
{
friend T;
private:
Base() {}
~Base() {}
};
class Finalclass : virtual public Base<Finalclass>
{
public:
Finalclass() {}
~Finalclass() {}
};
</span>
Finalclass就是那个不能被继承的类。
继承于Base,Base为虚基类,因为它是Base的友元,所以,它可以访问基类的私有构造函数,以及析构函数。编译运行时是正确的。也就是说,可以创建堆上的对象,并且可以构建栈上的对象。
三。含有指针成员的类的拷贝
题目:下面是一个数组类的声明和实现,分析这个类有什么问题,并针对问标提出集中解决方案。
<span style="font-family:Microsoft YaHei;font-size:14px;"> Template<typename T> class Array
{
public:
Array(unsigned arraySize):data(0), size(arraySize)
{
if(size > 0)
data = new T[size];
}
~Array()
{
if(data) delete[] data;
}
void setValue(unsigned index, const T& value)
{
if(index < size)
data[index] = value;
}
T getValue(unsigned index) const
{
if(index < size)
return data[index];
else
return T();
}
private:
T* data;
unsigned size;
}</span>
眼尖的朋友应该看出来一点端倪,此类只定义了带参数的构造函数,没有定义无参数的构造函数,还有没有定义拷贝构造函数和复制运算符的重载,系统将会自动生成一个,但是,恰恰系统就会坑了你,此类中有指针的数据成员,这是很危险的
编译器生成的缺省的构造拷贝函数和拷贝运算符的重载函数,对指针实行的是按位拷贝,仅仅只是拷贝指针的地址,而不会拷贝指针的内容。若执行Array A(10);Array B(A)时。A.data和B.data指向的同一地址。当A或者B中任意一个结束其生命周期调用析构函数时,会删除data。由于他们的data指向的是同一个地方,两个实例的data都被删除了。但另外一个实例并不知道它的data已经被删除了,当企图再次用它的 data的时候,程序就会不可避免地崩溃。
<span style="font-family:Microsoft YaHei;font-size:14px;">private:
Array(const Array& copy);
const Array& operator = (const Array& copy);</span>
但是这样的类并不是一个功能齐全的类,所以我们只能自己实现着两个函数来解决问题了:
<span style="font-family:Microsoft YaHei;font-size:14px;">rray(const Array& copy):data(0), size(copy.size)
{
if(size > 0)
{
data = new T[size];
for(int i = 0; i < size; ++ i)
setValue(i, copy.getValue(i));
}
}
const Array& operator = (const Array& copy)
{
if(this == ©)
return *this;
if(data != NULL)
{
delete []data;
data = NULL;
}
size = copy.size;
if(size > 0)
{
data = new T[size];
for(int i = 0; i < size; ++ i)
setValue(i, copy.getValue(i));
}
}</span>