面向对象编程安全作业
面向对象编程安全作业作业1:写一段静态成员不安全的代码并对提出解决方案。具体代码见P1_UnsafeStatic工程,下面对核心代码进行分析:class CRabbitpublic:CRabbit();CRabbit();static int our_n_carrot;void eat()Sleep(500);/吃萝卜需要时间our_n_carrot-;void collect()Sleep(600);/收集萝卜需要时间our_n_carrot+;void count_carrot()cout << "当前萝卜数:" << our_n_carrot << endl;这里模拟的是一个兔子的行为,包括吃萝卜、收集萝卜和数萝卜,这里有一个类静态成员变量,表示兔群当前萝卜数。但是,静态变量当被多线程操作时,就会变得很不安全,这里尝试用多只兔子来模拟这个过程。模拟兔子行为的代码如下:bool rabbit_act()CRabbit rabbit;while (1)if (CRabbit:our_n_carrot > 0)rabbit.eat();elserabbit.collect();rabbit.count_carrot();return true;这里定义的规则是:当一个兔子发现兔群的萝卜数大于0时,它就会吃掉一个萝卜;否则,它会去收集一个萝卜。最后,它会报出当前兔群的萝卜数,如此反复。最后在主函数中开三个“兔子”线程,观察运行结果如下图所示:图1-1 程序运行结果示意图这里可以看出,某些兔子已经把萝卜吃成负的了所以直接对类静态变量进行修改是存在问题的这里的解决方案就是每次只允许一个兔子操作萝卜,用一个全局的静态变量进行保护。核心代码如下:if (!CRabbit:m_b_busy)CRabbit:m_b_busy = true;if (CRabbit:our_n_carrot > 0)rabbit.eat();elserabbit.collect();rabbit.count_carrot();CRabbit:m_b_busy = false;图1-2 程序运行结果示意图(2)这里可以看到,如果加了标识变量进行保护时,萝卜的数量始终为0或者1,这样就不会出现问题。作业2:(1)在C和Java中怎样回收内存?(2)单例模式为什么能提高程序性能?有何劣势?C语言是使用delete或者free释放内存,delete用于释放new动态申请的空间,free用于释放malloc动态申请的空间。而到了其生命周期的变量,其内存会被自动回收。而java是调用finalise()是申请回收内存,至于何时被回收并不能被确定。单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。优点:实例控制,单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。灵活性,因为类控制了实例化过程,所以类可以灵活更改实例化过程。缺点:开销,虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。可能的开发混淆,使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new 关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。对象生存期,不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C+),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用。作业3:异常可以就地处理,也可以向客户端传递。 (1) 举出两个需要向客户端传递的异常的例子; 无法通过参数检查, 例如空值、参数取值范围等,因为参数的规则说明是作为方法签名的一部分发布的,调用者应该很清楚调用这个方法所需要的参数的规格,但是调用者现在传入的错误的参数,说明调用者本身这时候很可能就已经出现问题了。对参数进行检查本身也是一个编写一个方法的良好习惯,这可以保证方法本身在一个相对比较稳定可知道状态下工作,因此方法本身将更简单也更稳定。总之,拒绝非法的参数,并抛出异常。将可能导致以后的相关功能出错, 如果一个异常或者错误的参数将导致以后更多的异常或者以后的某个功能异常,那么不要延迟,立即抛出异常。例如有一个属性Filename用来保存将被保存的文件的名称,一个的方法Save将保存到Filename所指定的文件名中去,显然在给Filename属性赋值时,如果给出一个包含非法文件名字符的字符串,那么将导致 Save 方法中会有异常发生,不论 Save 方法是否对 Filename 再进行检查,这时候距离设定Filename的代码可能已经相当远了,因此在设定Filename的时候,就应该要进行这种有关的检查,如果文件名不合法,立即抛出异常,而不要等到以后再来发现这个问题。上述对参数进行检查其实很多也是这种情况,特别一些空值参数,将可能导致以后使用该参数的时候发生空值异常。(2) 举出两个可以就地处理的异常的例子。从集合中删除指定键值对应的元素,但指定的键值其实并不在集合中。这显然是一个意外情况,但是,就此继续下去,以后的程序应该还可以继续有效的运行,因为即使在正常情况下也不过是这个元素不再存在于集合中而已。返回一个整数值表示删除掉的元素的个数,如果没有找到给定的元素,则返回0表示没有元素被删除。这个例子的另一种情形是,当前集合元素因为某种原因被锁住不能删除元素,那么应该抛出一个异常表示删除失败,而不是返回0表示没有删除元素。用户输入格式不正确,如果能经过处理,使得可以正常读人,不影响后门的程序运行,就可以不必抛出异常。作业4:有一个try块放在for循环内,如果try块内跳出该循环,finally是否会执行?试编程举例。完整工程见P4_TestFinally,核心代码和运行结果如下:for (i=0; i<=5; i+)trybreak;catch (Exception e)finallySystem.out.printf("Finally被执行了");图4-1 程序运行结果这里可以看出,虽然运行了break,但是还是会执行1次finally之后,再跳循环。看来finally中的代码果然会被执行【为何C+没有finally】作业5:编写一个程序,客户输入一个数字,打印其平方。但是如果出错,程序不断提示客户重新输入,直到他输入正确为止。详细工程见P5_WhileTrue,核心代码和运行结果如下图所示:double d;bool flag = true;while (flag)if (cin >> d)cout << d << "的平方为:" << d * d << endl;flag = false;elsecout << "您的输入不正确,请输入一个数字:" << endl;cin.clear();cin.ignore(1000, 'n');图5-1 程序运行结果