笔试题资源整理(1)
1.Debug和Release build的区别,速度有差异吗?并说出原因。
解答:
一:Debug是调试版本,它包含调试信息,且不做任何优化,便于程序员调试程序。Release是发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。
二:只有Debug版的程序才能设置断点,单步执行,使用TRACE/ASSERT等调试输出语句。Release不包含任何调试信息,所以体积小,速度快。
三:在debug版报错,在release里面不一定报错,代码是一定有错的。
更多详细的介绍(http://blog.sina.com.cn/s/blog_6a4b57e30100mcrv.html)
2.死锁的四个必要条件
解答:
一:什么是死锁?
举一个例子:资源1,2都是不可剥夺的资源,现在进程C已经申请资源1,进程D已经申请资源2,接下来的操作时,进程C要用到资源2,进程D恰好也要用到资源1,这就会引发了死锁(这里的资源包括了软资源(代码块)和硬资源(如扫描仪),资源可分为可剥夺和不可剥夺两种,可剥夺资源引起的死锁可以由系统重新分配资源来解决,一般来说,死锁都是指由不可剥夺的资源引起的)。
二:死锁的必要条件
1).互斥条件:资源不能被共享,只能由一个进程使用
2).请求与保持条件:如上述的例子所提到的,进程C和D都已经分别得到资源1,2.但是可以再次申请新的资源(进程C又申请资源2,进程D又申请资源1)
3).非剥夺条件:某进程已经分配的资源不能被强制剥夺(例如进程C分配到的资源1不能被进程D强制剥夺)
4).循环等待:系统中若干进程组成环路,该环路中每个进程都在等待相邻的进程正占用的资源(进程C在等待进程D拥有的资源2,进程D又在等待进程C拥有的资源1)。
三:处理死锁的策略
1).忽略该问题(鸵鸟算法,原理可以用掩耳盗铃来解释)
2).检测死锁并恢复
3).仔细地对资源进行动态分配,以避免死锁。
4).通过破除死锁四个必要条件之一,来防止死锁。
3.“assignment operator”and “copy constructor”的差异
解答:
写法一:T c=a+b或T c(a+b)调用了copy constructor
写法二:T c; c = a+b;调用的是assignment operator
可以先这样通俗地理解:如果一个新的对象被定义(就象上面那行代码中的 c),一个构造函数必须被调用;它不可能是一个赋值。如果没有新的对象被定义(就象上面那行 "a= b" 代码中),没有构造函数能被调用,所以它就是一个赋值。
在inside the c++ object model 6.3文中对他们的解释是:
对于写法1,编译器会产生一个临时对象,放置a+b,然后调用c的copy constructor把临时对象当做c的初始值:
/编译器附加代码:
T _temp; //产生临时对象
_temp = a+b; //放置a+b的值
T c(_temp); //调用c的copy constructor
还有一种可能是直接以拷贝构造的方式,将a+b的值放到c中。
T c(a+b);
或者实行NRV优化:【优化这里不懂】
_result.T::T();
//直接计算_result
C++standard允许编译器厂商有完全的自由度,但是由于市场的竞争,几乎保证任何表达式如果有这种形式:
T c = a+b;
那实现时根本不需要产生一个临时对象。
但是对于写法2,不能忽略临时对象。它会导致下面结果:
T temp;
temp.operator+(a,b);
c.operator=(temp);
temp.T::~T();
注意,此时在写法1中的"直接以拷贝构造方式来去除临时对象"或"以NRV优化来消除临时对象"的方法被作用于临时对象temp上。导致只能消除T temp = a+b此表达式的临时对象,而真正的临时对象temp无法被消除。
不管哪一种情况,直接传递c到运算符函数是有问题的。由于运算符函数并不为外加参数调用一个destructor(它期望一块新鲜的内存),所以必须在此调用前先调用destructor。如果将c直接传递到运算符函数中,必须为c调用destructor:【不是很懂】
//C++伪码
c.T::~T();
c.T::T(a+b); //以destructor和copy constructor来取代copy assignment operator
因为copy constructor、destructor以及copy assignment operator都可以由使用者供应,因此不能保证上述两个操作符合编译器所需要的语意,因此以一连串的destructor和copy constructor来取代assignment一般而言是不安全的,而且会产生临时对象。
因此T c = a+b;
总是比下面的操作更有效率地被编译器转换:
T c;
c= a+b;
注:何为新鲜的内存?意指只分配好的内存,尚未赋值。例如: char *p=new char[size_t];就是一块新鲜的内存,因为它只执行了下面一步:
char *p = _new (size_t); //调用operator new 来分配内存
而对于下面这一步:
*p = char::constructor() //不会进行,因为char无default 构造函数,或者说它所产生的constructor 是trivial。
附加学习:默认拷贝构造函数:浅拷贝、深拷贝(参见http://blog.163.com/cocoa_20/blog/static/25396006200973174129609/)也可以书上P79的例子.
构造函数的使用:1.当类中定义了带有参数的构造函数后,系统将不再给它提供默认的构造函数。
2.在一个类中,当无参数的构造函数和带默认参数的构造函数重载时,有可能产生二义性,例如:
class X{ public:X(); X(int =0);}
int main(){ X one(10);//调用带默认参数的构造函数
X two;//存在二义性,因为它既可以调用无参数的构造函数也可以调用带默认参数的构造函数。}
4.What's serialization,how does it work in .net or Java
Serialization是.NET中一种实现对象持久性(Persistent)的机制。它是一个将对象中的数据转换成一个单一元素(通常是 Stream)的过程。它的逆过程是Deserialization。Serialization的核心概念是将一个对象的所有数据看作一个独立的单元。
在java中,为序列化一个对象,首先要创建某些OutputStream对象,然后将其封装到 ObjectOutputStream对象内。此时,只需调用writeObject()即可完成对象的序列化,并将其发送给OutputStream。 相反的过程是将一个InputStream封装到ObjectInputStream内,然后调用readObject()。和往常一样,我们最后获得的 是指向一个上溯造型Object的句柄,所以必须下溯造型,以便能够直接设置。
.net 中, 通常,一个Serialization过程会由formatter(例如BinaryFormatter)的Serialize方法引发。对象的Serialization过程按照以下规则进行:
1)、 检测以确保formatter是否拥有一个代理选择器(surrogate selector)。如果有,检查代理选择器是否持有给定的对象类型。如果有,ISerializable.GetObjectData被调用。
2)、 如果formatter没有代理选择器,或者代理选择器没有对象类型,检查对象是否被用Serializable属性标记。如果没有,则抛出SerializationException异常。
3)、 如果对象被标记为Serializable,检查对象是否实现了ISerializable接口。如果实现了此接口,则GetObjectData被调用。
4)、 如果对象没有实现ISerializable接口,则使用默认的序列化策略,来序列化没有用NonSerialized属性标记的域。
Java的序列化算法
序列化算法一般会按步骤做如下事情:
◆将对象实例相关的类元数据输出。
◆递归地输出类的超类描述直到不再有超类。
◆类元数据完了以后,开始从最顶层的超类开始输出对象实例的实际数据值。
◆从上至下递归输出实例的数据