程式語言面試常用題

fudanstar發表於2016-03-22

1 .虛擬函式

虛擬函式(impure virtual),C++的虛擬函式主要作用是“執行時多型”,父類中提供虛擬函式的實現,為子類提供預設的函式實現。子類可以重寫父類的虛擬函式實現子類的特殊化。

純虛擬函式(pure virtual),C++中包含純虛擬函式的類,被稱為是“抽象類”。抽象類不能使用new出物件,只有實現了這個純虛擬函式的子類才能new出物件。C++中的純虛擬函式更像是“只提供申明,沒有實現”,是對子類的約束,是“介面繼承”。C++中的純虛擬函式也是一種“執行時多型”。

class A
{
public:
    virtual void out1(string s)=0;//純虛擬函式
    virtual void out2(string s)
    {
        cout<<"A(out2):"<<s<<endl;
    }
};
#include <iostream>
using namespace std;
class A
{
public:
    virtual void out1()=0;  ///由子類實現
    virtual ~A(){};
    virtual void out2() ///預設實現
    {
        cout<<"A(out2)"<<endl;
    }
    void out3() ///強制實現
    {
        cout<<"A(out3)"<<endl;
    }
};
class B:public A
{
public:
    virtual ~B(){};
    void out1()
    {
        cout<<"B(out1)"<<endl;
    }
    void out2()
    {
        cout<<"B(out2)"<<endl;
    }
    void out3()
    {
        cout<<"B(out3)"<<endl;
    }
};
int main()
{
    A *ab=new B;
    ab->out1();
    ab->out2();
    ab->out3();
    cout<<"************************"<<endl;
    B *bb=new B;
    bb->out1();
    bb->out2();
    bb->out3();

    delete ab;
    delete bb;
    return 0;
}

這裡寫圖片描述

2 . java的反射機制淺談

    JAVA反射機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法;這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為java語言的反射機制。

java.lang.Class

     Java程式在執行時,Java執行時系統一直對所有的物件進行所謂的執行時型別標識。這項資訊紀錄了每個物件所屬的類。虛擬機器通常使用執行時型別資訊選準正確方法去執行,用來儲存這些型別資訊的類是Class類。
import java.lang.reflect.Array;   
import java.lang.reflect.Constructor;   
import java.lang.reflect.Field;   
import java.lang.reflect.Method;   


/**  
 * Java Reflection Cookbook  
 *  
 * @author Michael Lee  
 * @since 2006-8-23  
 * @version 0.1a  
 */  

public class Reflection {   
    /**  
     * 得到某個物件的公共屬性  
     *  
     * @param owner, fieldName  
     * @return 該屬性物件  
     * @throws Exception  
     *  
     */  
    public Object getProperty(Object owner, String fieldName) throws Exception {   
        Class ownerClass = owner.getClass();   

        Field field = ownerClass.getField(fieldName);   

        Object property = field.get(owner);   

        return property;   
    }   

    /**  
     * 得到某類的靜態公共屬性  
     *  
     * @param className   類名  
     * @param fieldName   屬性名  
     * @return 該屬性物件  
     * @throws Exception  
     */  
    public Object getStaticProperty(String className, String fieldName)   
            throws Exception {   
        Class ownerClass = Class.forName(className);   

        Field field = ownerClass.getField(fieldName);   

        Object property = field.get(ownerClass);   

        return property;   
    }   


    /**  
     * 執行某物件方法  
     *  
     * @param owner  
     *            物件  
     * @param methodName  
     *            方法名  
     * @param args  
     *            引數  
     * @return 方法返回值  
     * @throws Exception  
     */  
    public Object invokeMethod(Object owner, String methodName, Object[] args)   
            throws Exception {   

        Class ownerClass = owner.getClass();   

        Class[] argsClass = new Class[args.length];   

        for (int i = 0, j = args.length; i < j; i++) {   
            argsClass[i] = args[i].getClass();   
        }   

        Method method = ownerClass.getMethod(methodName, argsClass);   

        return method.invoke(owner, args);   
    }   


      /**  
     * 執行某類的靜態方法  
     *  
     * @param className  
     *            類名  
     * @param methodName  
     *            方法名  
     * @param args  
     *            引數陣列  
     * @return 執行方法返回的結果  
     * @throws Exception  
     */  
    public Object invokeStaticMethod(String className, String methodName,   
            Object[] args) throws Exception {   
        Class ownerClass = Class.forName(className);   

        Class[] argsClass = new Class[args.length];   

        for (int i = 0, j = args.length; i < j; i++) {   
            argsClass[i] = args[i].getClass();   
        }   

        Method method = ownerClass.getMethod(methodName, argsClass);   

        return method.invoke(null, args);   
    }   



    /**  
     * 新建例項  
     *  
     * @param className  
     *            類名  
     * @param args  
     *            建構函式的引數  
     * @return 新建的例項  
     * @throws Exception  
     */  
    public Object newInstance(String className, Object[] args) throws Exception {   
        Class newoneClass = Class.forName(className);   

        Class[] argsClass = new Class[args.length];   

        for (int i = 0, j = args.length; i < j; i++) {   
            argsClass[i] = args[i].getClass();   
        }   

        Constructor cons = newoneClass.getConstructor(argsClass);   

        return cons.newInstance(args);   

    }   



    /**  
     * 是不是某個類的例項  
     * @param obj 例項  
     * @param cls 類  
     * @return 如果 obj 是此類的例項,則返回 true  
     */  
    public boolean isInstance(Object obj, Class cls) {   
        return cls.isInstance(obj);   
    }   

    /**  
     * 得到陣列中的某個元素  
     * @param array 陣列  
     * @param index 索引  
     * @return 返回指定陣列物件中索引元件的值  
     */  
    public Object getByArray(Object array, int index) {   
        return Array.get(array,index);   
    }   
}  

3 . 執行緒同步

public class TraditionalThreadSynchronized {
    public static void main(String[] args) {
        final Outputter output = new Outputter();
        new Thread() {
            public void run() {
                output.output("zhangsan");
            }
        }.start();      
        new Thread() {
            public void run() {
                output.output("lisi");
            }
        }.start();
    }
}
class Outputter {
    public void output(String name) {
        // TODO 為了保證對name的輸出不是一個原子操作,這裡逐個輸出name的每個字元
        for(int i = 0; i < name.length(); i++) {
            System.out.print(name.charAt(i));
            // Thread.sleep(10);
        }
    }
}

( 1). 使用synchronized將需要互斥的程式碼包含起來,並上一把鎖。

{
    synchronized (this) {
        for(int i = 0; i < name.length(); i++) {
            System.out.print(name.charAt(i));
        }
    }
}

這把鎖必須是需要互斥的多個執行緒間的共享物件,像下面的程式碼是沒有意義的。

{
    Object lock = new Object();
    synchronized (lock) {
        for(int i = 0; i < name.length(); i++) {
            System.out.print(name.charAt(i));
        }
    }
}

每次進入output方法都會建立一個新的lock,這個鎖顯然每個執行緒都會建立,沒有意義。
(2) . 將synchronized加在需要互斥的方法上。

public synchronized void output(String name) {
    // TODO 執行緒輸出方法
    for(int i = 0; i < name.length(); i++) {
        System.out.print(name.charAt(i));
    }
}

這種方式就相當於用this鎖住整個方法內的程式碼塊,如果用synchronized加在靜態方法上,就相當於用××××.class鎖住整個方法內的程式碼塊。
(3) . volatile
volatile是第二種Java多執行緒同步的機制,根據JLS(Java LanguageSpecifications)的說法,一個變數可以被volatile修飾,在這種情況下記憶體模型(主記憶體和執行緒工作記憶體)確保所有執行緒可以看到一致的變數值,來看一段程式碼:

class Test {
    static int i = 0, j = 0;
    static void one() {
        i++;
        j++;
    }
    static void two() {
        System.out.println("i=" + i + " j=" + j);
    }
}

一些執行緒執行one方法,另一些執行緒執行two方法,two方法有可能列印出j比i大的值,按照之前分析的執行緒執行過程分析一下:

    1. 將變數i從主記憶體拷貝到工作記憶體;
    2. 改變i的值;
    3. 重新整理主記憶體資料;
    4. 將變數j從主記憶體拷貝到工作記憶體;
    5. 改變j的值;
    6. 重新整理主記憶體資料;

這個時候執行two方法的執行緒先讀取了主存i原來的值又讀取了j改變後的值,這就導致了程式的輸出不是我們預期的結果,要阻止這種不合理的行為的一種方式是在one方法和two方法前面加上synchronized修飾符:

class Test {
    static int i = 0, j = 0;
    static synchronized void one() {
        i++;
        j++;
    }
    static synchronized void two() {
        System.out.println("i=" + i + " j=" + j);
    }
}

根據前面的分析,我們可以知道,這時one方法和two方法再也不會併發的執行了,i和j的值在主記憶體中會一直保持一致,並且two方法輸出的也是一致的。另一種同步的機制是在共享變數之前加上volatile:

class Test {
    static volatile int i = 0, j = 0;
    static void one() {
        i++;
        j++;
    }
    static void two() {
        System.out.println("i=" + i + " j=" + j);
    }
}

one方法和two方法還會併發的去執行,但是加上volatile可以將共享變數i和j的改變直接響應到主記憶體中,這樣保證了主記憶體中i和j的值一致性,然而在執行two方法時,在two方法獲取到i的值和獲取到j的值中間的這段時間,one方法也許被執行了好多次,導致j的值會大於i的值。所以volatile可以保證記憶體可見性,不能保證併發有序性。

4 .抽象類和介面的區別

1.語法層面上的區別

   1)抽象類可以提供成員方法的實現細節,而介面中只能存在public abstract 方法;
   2)抽象類中的成員變數可以是各種型別的,而介面中的成員變數只能是public static final型別的;
   3)介面中不能含有靜態程式碼塊以及靜態方法,而抽象類可以有靜態程式碼塊和靜態方法;
   4)一個類只能繼承一個抽象類,而一個類卻可以實現多個介面。

2.設計層面上的區別

(1)抽象類是對一種事物的抽象,即對類抽象,而介面是對行為的抽象。抽象類是對整個類整體進行抽象,包括屬性、行為,但是介面卻是對類區域性(行為)進行抽象。舉個簡單的例子,飛機和鳥是不同類的事物,但是它們都有一個共性,就是都會飛。那麼在設計的時候,可以將飛機設計為一個類Airplane,將鳥設計為一個類Bird,但是不能將 飛行 這個特性也設計為類,因此它只是一個行為特性,並不是對一類事物的抽象描述。此時可以將 飛行 設計為一個介面Fly,包含方法fly( ),然後Airplane和Bird分別根據自己的需要實現Fly這個介面。然後至於有不同種類的飛機,比如戰鬥機、民用飛機等直接繼承Airplane即可,對於鳥也是類似的,不同種類的鳥直接繼承Bird類即可。從這裡可以看出,繼承是一個 "是不是"的關係,而 介面 實現則是 "有沒有"的關係。如果一個類繼承了某個抽象類,則子類必定是抽象類的種類,而介面實現則是有沒有、具備不具備的關係,比如鳥是否能飛(或者是否具備飛行這個特點),能飛行則可以實現這個介面,不能飛行就不實現這個介面。

(2)設計層面不同,抽象類作為很多子類的父類,它是一種模板式設計。而介面是一種行為規範,它是一種輻射式設計。什麼是模板式設計?最簡單例子,大家都用過ppt裡面的模板,如果用模板A設計了ppt B和ppt C,ppt B和ppt C公共的部分就是模板A了,如果它們的公共部分需要改動,則只需要改動模板A就可以了,不需要重新對ppt B和ppt C進行改動。而輻射式設計,比如某個電梯都裝了某種報警器,一旦要更新報警器,就必須全部更新。也就是說對於抽象類,如果需要新增新的方法,可以直接在抽象類中新增具體的實現,子類可以不進行變更;而對於介面則不行,如果介面進行了變更,則所有實現這個介面的類都必須進行相應的改動。

5 .Spring IOC

IOC容器的概念
IOC容器就是具有依賴注入功能的容器,IOC容器負責例項化、定位、配置應用程式中的物件及建立這些物件間的依賴。應用程式無需直接在程式碼中new相關的物件,應用程式由IOC容器進行組裝。在Spring中BeanFactory是IOC容器的實際代表者。

Spring IOC容器如何知道哪些是它管理的物件呢?這就需要配置檔案,Spring IOC容器通過讀取配置檔案中的配置後設資料,通過後設資料對應用中的各個物件進行例項化及裝配。一般使用基於xml配置檔案進行配置後設資料,而且Spring與配置檔案完全解耦的,可以使用其他任何可能的方式進行配置後設資料,比如註解、基於java檔案的、基於屬性檔案的配置都可以。
那Spring IOC容器管理的物件叫什麼呢?

Bean的概念
由IOC容器管理的那些組成你應用程式的物件我們就叫它Bean, Bean就是由Spring容器初始化、裝配及管理的物件,除此之外,bean就與應用程式中的其他物件沒有什麼區別了。那IOC怎樣確定如何例項化Bean、管理Bean之間的依賴關係以及管理Bean呢?這就需要配置後設資料,在Spring中由BeanDefinition代表,後邊會詳細介紹,配置後設資料指定如何例項化Bean、如何組裝Bean等。概念知道的差不多了,讓我們來做個簡單的例子。

package com.ljq.test;

public interface HelloService {
    public void sayHello();  
}

package com.ljq.test;

public class HelloServiceImpl implements HelloService{
    public void sayHello(){
        System.out.println("Hello World!");
    }
}

public class Excute{
     public HelloService helloService;
     void setHelloService(HelloService helloService){//set依賴註解,也可以使用構造依賴註解
         this.helloService=helloService;
     }
     void sayHello()
     {
         helloService.sayHello();
     }
}

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans        
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context                
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <!-- id 表示元件的名字,class表示元件類 -->
    <bean id="helloService" class="com.ljq.test.HelloServiceImpl" />
    <bean id="excute" 
        class="com.ljq.test.Excute">
        <property id="helloService">
            <ref bean="helloService" />
        </property>
    </bean>
</beans>

package com.ljq.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 測試
 * 
 * @author 林計欽
 * @version 1.0 2013-11-4 下午10:56:04
 */
public class HelloServiceTest {
    @Test
    public void testHelloWorld() {
        // 1、讀取配置檔案例項化一個IOC容器
        ApplicationContext context = new ClassPathXmlApplicationContext("helloworld.xml");
        // 2、從容器中獲取Bean,注意此處完全“面向介面程式設計,而不是面向實現”
        Excute excute= context.getBean("excute", Excute.class);
        // 3、執行業務邏輯
        excute.sayHello();
    }
}

在上面的程式碼例子中就包括控制反轉和set依賴注入。
 

相關文章