1. 程式人生 > >Android AOP介紹及實現原理

Android AOP介紹及實現原理

深入理解Android之AOP

一、閒談AOP

大家都知道OOP,即ObjectOriented Programming,面向物件程式設計。而本文要介紹的是AOP。AOP是Aspect Oriented Programming的縮寫,中譯文為面向切向程式設計。OOP和AOP是什麼關係呢?首先:

l OOP和AOP都是方法論。我記得在剛學習C++的時候,最難學的並不是C++的語法,而是C++所代表的那種看問題的方法,即OOP。同樣,今天在AOP中,我發現其難度並不在利用AOP幹活,而是從AOP的角度來看待問題,設計解決方法。這就是為什麼我特意強調AOP是一種方法論的原因!
l 在OOP的世界中,問題或者功能都被劃分到一個一個的模組裡邊。每個模組專心幹自己的事情,模組之間通過設計好的介面互動。從圖示來看,OOP世界中,最常見的表示比如:

圖中所示為AndroidFramework中的模組。OOP世界中,大家畫的模組圖基本上是這樣的,每個功能都放在一個模組裡。非常好理解,而且確實簡化了我們所處理問題的難度。

OOP的精髓是把功能或問題模組化,每個模組處理自己的家務事。但在現實世界中,並不是所有問題都能完美得劃分到模組中。舉個最簡單而又常見的例子:現在想為每個模組加上日誌功能,要求模組執行時候能輸出日誌。在不知道AOP的情況下,一般的處理都是:先設計一個日誌輸出模組,這個模組提供日誌輸出API,比如Android中的Log類。然後,其他模組需要輸出日誌的時候呼叫Log類的幾個函式,比如e(TAG,…),w(TAG,…),d(TAG,…),i(TAG,…)等。

在沒有接觸AOP之前,包括我在內,想到的解決方案就是上面這樣的。但是,從OOP角度看,除了日誌模組本身,其他模組的家務事絕大部分情況下應該都不會包含日誌輸出功能。什麼意思?以ActivityManagerService為例,你能說它的家務事裡包含日誌輸出嗎?顯然,ActivityManagerService的功能點中不包含輸出日誌這一項。但實際上,軟體中的眾多模組確實又需要列印日誌。這個日誌輸出功能,從整體來看,都是一個面上的。而這個面的範圍,就不侷限在單個模組裡了,而是橫跨多個模組。

l 在沒有AOP之前,各個模組要列印日誌,就是自己處理。反正日誌模組的那幾個API都已經寫好了,你在其他模組的任何地方,任何時候都可以呼叫。功能是得到了滿足,但是好像沒有Oriented的感覺了。是的,隨意加日誌輸出功能,使得其他模組的程式碼和日誌模組耦合非常緊密。而且,將來要是日誌模組修改了API,則使用它們的地方都得改。這種搞法,一點也不酷。
AOP的目標就是解決上面提到的不cool的問題。在AOP中:

l 第一,我們要認識到OOP世界中,有些功能是橫跨並嵌入眾多模組裡的,比如列印日誌,比如統計某個模組中某些函式的執行時間等。這些功能在各個模組裡分散得很厲害,可能到處都能見到。
l 第二,AOP的目標是把這些功能集中起來,放到一個統一的地方來控制和管理。如果說,OOP如果是把問題劃分到單個模組的話,那麼AOP就是把涉及到眾多模組的某一類問題進行統一管理。比如我們可以設計兩個Aspects,一個是管理某個軟體中所有模組的日誌輸出的功能,另外一個是管理該軟體中一些特殊函式呼叫的許可權檢查。

二、沒有AOP的例子

先來看沒有AOP的情況下,程式碼怎麼寫。

public class AopDemoActivity extends Activity {  
   private static final String TAG = "AopDemoActivity";  
  onCreate,onStart,onRestart,onPause,onResume,onStop,onDestory返回前,都輸出一行日誌  
   protected void onCreate(Bundle savedInstanceState) {  
       super.onCreate(savedInstanceState);  
       setContentView(R.layout.layout_main);  
       Log.e(TAG,"onCreate");  
    }  
   protected void onStart() {  
       super.onStart();  
        Log.e(TAG, "onStart");  
    }  
   protected void onRestart() {  
       super.onRestart();  
        Log.e(TAG, "onRestart");  
    }  
    protectedvoid onResume() {  
       super.onResume();  
        Log.e(TAG, "onResume");  
   checkPhoneState會檢查app是否申明瞭android.permission.READ_PHONE_STATE許可權  
        checkPhoneState();  
    }  
   protected void onPause() {  
       super.onPause();  
        Log.e(TAG, "onPause");  
    }  
   protected void onStop() {  
       super.onStop();  
        Log.e(TAG, "onStop");  
    }  
   protected void onDestroy() {  
       super.onDestroy();  
        Log.e(TAG, "onDestroy");  
    }  
   private void checkPhoneState(){  
       if(checkPermission("android.permission.READ_PHONE_STATE")== false){  
           Log.e(TAG,"have no permission to read phone state");  
           return;  
        }  
       Log.e(TAG,"Read Phone State succeed");  
       return;  
    }  
   private boolean checkPermission(String permissionName){  
       try{  
           PackageManager pm = getPackageManager();  
          //呼叫PackageMangaer的checkPermission函式,檢查自己是否申明使用某許可權  
           int nret = pm.checkPermission(permissionName,getPackageName());  
           return nret == PackageManager.PERMISSION_GRANTED;  
        }......  
    }  
}  

程式碼很簡單。但是從這個小例子中,你也會發現要是這個程式比較複雜的話,到處都加Log,或者在某些特殊函式加許可權檢查的程式碼,真的是一件挺繁瑣的事情。

三、使用AspectJ在Android中實現Aop

AOP雖然是方法論,但就好像OOP中的Java一樣,一些先行者也開發了一套語言來支援AOP。目前用得比較火的就是AspectJ了,它是一種幾乎和Java完全一樣的語言,而且完全相容Java(AspectJ應該就是一種擴充套件Java,但它不是像Groovy[1]那樣的拓展。)。當然,除了使用AspectJ特殊的語言外,AspectJ還支援原生的Java,只要加上對應的AspectJ註解就好。

用AspectJ的方法很簡單,首先是定義一個DebugLog註解:

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.CLASS;

@Target({TYPE, METHOD, CONSTRUCTOR}) @Retention(CLASS)
public @interface DebugLog {
}

接下來就是使用AspectJ的方式,其設定PointCut:

@Aspect
public class Hugo {
    //帶有DebugLog註解的所有類
    @Pointcut("within(@com.example.aoplib.DebugLog *)")
    public void withinAnnotatedClass() {}

    //在帶有DebugLog註解的所有類,除去synthetic修飾的方法
    @Pointcut("execution(!synthetic * *(..)) && withinAnnotatedClass()")
    public void methodInsideAnnotatedType() {}

    //在帶有DebugLog註解的所有類,除去synthetic修飾的構造方法
    @Pointcut("execution(!synthetic *.new(..)) && withinAnnotatedClass()")
    public void constructorInsideAnnotatedType() {}

    //在帶有DebugLog註解的方法
    @Pointcut("execution(@com.example.aoplib.DebugLog * *(..)) || methodInsideAnnotatedType()")
    public void method() {}

   //在帶有DebugLog註解的構造方法
    @Pointcut("execution(@com.example.aoplib.DebugLog *.new(..)) || constructorInsideAnnotatedType()")
    public void constructor() {}
    ....
}

上面程式碼的註釋中,已經說明了每個PointCut的意義,也就是說,AspectJ會去尋找帶有@DebugLog註解的類,或者帶有@DebugLog註解的方法和構造方法。

接著是定義Advice:

@Around("method() || constructor()")
    public Object logAndExecute(ProceedingJoinPoint joinPoint) throws Throwable {
        //執行方法前,做些什麼
        enterMethod(joinPoint);

        long startNanos = System.nanoTime();
        //執行原方法
        Object result = joinPoint.proceed();
        long stopNanos = System.nanoTime();
        long lengthMillis = TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos);
        //執行方法後,做些什麼
        exitMethod(joinPoint, result, lengthMillis);

        return result;
    }
這些程式碼,和Hugo中的一模一樣,我將其抽取,構成一個簡單的library,叫做aoplib,看下圖:

這裡寫圖片描述

四、Android AOP實現原理全解析

我們本部落格的重點是瞭解清楚AOP的整個實現流程,是直接使用的別人的程式碼,程式碼也是在別人的部落格中直接下載的,地址如下:
使用AspectJ在Android中實現Aop
Activity就一個,執行時就直接呼叫TestMain.TestAll()日誌列印。我們先來看一下作者分module的用意。整個project分為aoplib、app、buildsrc、libinlib、testlib五個module,各module的意思應該也比較清楚,aoplib就是實現AOP功能的模組,app是本專案的啟動模組,buildsrc是用來構建專案的模組,libinlib中作者只提供了一個TestLog類,而且只有一個方法,目的是用來測試AOP功能的,最後一個testlib是作者實現自己意圖的模組,所有的測試類都是在這裡的。我們要分析的重點就是aoplib和testlib這兩個module了。
我們從程式的執行過程來一步步分析,首先看一下MainActivity類的onCreate方法,請注意,作者為這個方法上加了一個@DebugLog註解,@DebugLog註解是自己實現的,實現程式碼如下,非常簡單:

package com.example.aoplib;  

import java.lang.annotation.Retention;  
import java.lang.annotation.Target;  

import static java.lang.annotation.ElementType.CONSTRUCTOR;  
import static java.lang.annotation.ElementType.METHOD;  
import static java.lang.annotation.ElementType.TYPE;  
import static java.lang.annotation.RetentionPolicy.CLASS;  

@Target({TYPE, METHOD, CONSTRUCTOR})  
@Retention(CLASS)  
public @interface DebugLog {  
}  

從這個註釋介面的定義上,我們可以看到,它的目標是使用在TYPE(介面、類、列舉、註解),METHOD(方法),CONSTRUCTOR(建構函式)三種類型上的,當前就是使用在MainActivity的onCreate方法上的。我們可以來看一下使用Aspect編譯後的MainActivity的class檔案:
這裡寫圖片描述
從這裡大家可以非常清楚的看到,MainActivity的onCreate方法已經被替換了,它是按照Aspect編譯規則生成的,我們可以再來看看其他帶有@DebugLog註解的類或者方法對應生成的class檔案,比如TestMethodClass類的class檔案,整個檔案程式碼如下:

public class TestMethodClass {  
    public TestMethodClass() {  
        (new Thread() {  
            @DebugLog  
            public void run() {  
                JoinPoint var2 = Factory.makeJP(ajc$tjp_0, this, this);  
                Hugo var10000 = Hugo.aspectOf();  
                Object[] var3 = new Object[]{this, var2};  
                var10000.logAndExecute((new TestMethodClass$1$AjcClosure1(var3)).linkClosureAndJoinPoint(69648));  
            }  

            static {  
                ajc$preClinit();  
            }  
        }).start();  
    }  

    @DebugLog  
    public void spendTime1ms() {  
        JoinPoint var2 = Factory.makeJP(ajc$tjp_0, this, this);  
        Hugo var10000 = Hugo.aspectOf();  
        Object[] var3 = new Object[]{this, var2};  
        var10000.logAndExecute((new TestMethodClass$AjcClosure1(var3)).linkClosureAndJoinPoint(69648));  
    }  

    @DebugLog  
    public static void spendTime2ms() {  
        JoinPoint var1 = Factory.makeJP(ajc$tjp_1, (Object)null, (Object)null);  
        Hugo var10000 = Hugo.aspectOf();  
        Object[] var2 = new Object[]{var1};  
        var10000.logAndExecute((new TestMethodClass$AjcClosure3(var2)).linkClosureAndJoinPoint(65536));  
    }  

    @DebugLog  
    public final void spendTime3ms() {  
        JoinPoint var2 = Factory.makeJP(ajc$tjp_2, this, this);  
        Hugo var10000 = Hugo.aspectOf();  
        Object[] var3 = new Object[]{this, var2};  
        var10000.logAndExecute((new TestMethodClass$AjcClosure5(var3)).linkClosureAndJoinPoint(69648));  
    }  

    static {  
        ajc$preClinit();  
    }  
}  

在這個class檔案中,我們可以看到,run()、spendTime1ms()、spendTime2ms()、spendTime3ms()幾個方法全部都是這樣樣式,每個方法一共四句,第一句,構建一個切點JoinPoint,第二句呼叫Hugo.aspectOf()獲取我們自己定義的Aspect處理類,第三句構造引數陣列Object[],第四句呼叫當前類的相應方法。看到這裡我們基本就明白AOP的原理了,它就是利用我們自己實現的一個註解,將所有的切點集中在一個地方處理的,這樣,就可以把多個切點放在一起統一處理了,非常的方便!
下面我們就來分析一個方法的執行過程,此專案中其他方法的實現是完全一樣的,我們就以TestMethodClass類的spendTime1ms()為例來展開我們的分析,在編譯完的class檔案中,首先構造一個JoinPoint切點,Factory.makeJP()方法的實現就是使用傳入的引數直接構造一個JoinPointImpl物件,第二句就是獲取當前的Aspect處理類物件Hugo,此類必須要帶有@Aspect註解,第三句就是構造方法執行的陣列物件,第四句執行Aspect處理類的入口方法logAndExecute,Hugo類的完整程式碼如下:

@Aspect  
public class Hugo {  
    @Pointcut("within(@com.example.aoplib.DebugLog *)")  
    public void withinAnnotatedClass() {}  

    @Pointcut("execution(!synthetic * *(..)) && withinAnnotatedClass()")  
    public void methodInsideAnnotatedType() {}  

    @Pointcut("execution(!synthetic *.new(..)) && withinAnnotatedClass()")  
    public void constructorInsideAnnotatedType() {}  

    @Pointcut("execution(@com.example.aoplib.DebugLog * *(..)) || methodInsideAnnotatedType()")  
    public void method() {}  

    @Pointcut("execution(@com.example.aoplib.DebugLog *.new(..)) || constructorInsideAnnotatedType()")  
    public void constructor() {}  

    @Around("method() || constructor()")  
    public Object logAndExecute(ProceedingJoinPoint joinPoint) throws Throwable {  
        enterMethod(joinPoint);  

        long startNanos = System.nanoTime();  
        Object result = joinPoint.proceed();  
        long stopNanos = System.nanoTime();  
        long lengthMillis = TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos);  

        exitMethod(joinPoint, result, lengthMillis);  

        return result;  
    }  



    private static void enterMethod(JoinPoint joinPoint) {  

        CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature();  

        Class<?> cls = codeSignature.getDeclaringType();  
        String methodName = codeSignature.getName();  
        String[] parameterNames = codeSignature.getParameterNames();  
        Object[] parameterValues = joinPoint.getArgs();  

        StringBuilder builder = new StringBuilder("\u21E2 ");  
        builder.append(methodName).append('(');  
        for (int i = 0; i < parameterValues.length; i++) {  
            if (i > 0) {  
                builder.append(", ");  
            }  
            builder.append(parameterNames[i]).append('=');  
            builder.append(Strings.toString(parameterValues[i]));  
        }  
        builder.append(')');  

        if (Looper.myLooper() != Looper.getMainLooper()) {  
            builder.append(" [Thread:\"").append(Thread.currentThread().getName()).append("\"]");  
        }  

        Log.v(asTag(cls), builder.toString());  

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {  
            final String section = builder.toString().substring(2);  
            Trace.beginSection(section);  
        }  
    }  

    private static void exitMethod(JoinPoint joinPoint, Object result, long lengthMillis) {  

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {  
            Trace.endSection();  
        }  

        Signature signature = joinPoint.getSignature();  

        Class<?> cls = signature.getDeclaringType();  
        String methodName = signature.getName();  
        boolean hasReturnType = signature instanceof MethodSignature  
                && ((MethodSignature) signature).getReturnType() != void.class;  

        StringBuilder builder = new StringBuilder("\u21E0 ")  
                .append(methodName)  
                .append(" [")  
                .append(lengthMillis)  
                .append("ms]");  

        if (hasReturnType) {  
            builder.append(" = ");  
            builder.append(Strings.toString(result));  
        }  

        Log.v(asTag(cls), builder.toString());  
    }  

    private static String asTag(Class<?> cls) {  
        if (cls.isAnonymousClass()) {  
            return asTag(cls.getEnclosingClass());  
        }  
        return cls.getSimpleName();  
    }  
}  

可以看到,在logAndExecute方法的處理中就是列印了日誌而已,當然,我們也可以根據我們自己需求實現不同的功能,比如許可權檢查,如果許可權OK,就正常執行,如果不OK,則直接在這裡丟擲異常。
好了,AOP的完整過程我們已經瞭解了,可以看出,整個執行過程還是比較簡單的,就是使用了一個註解把我們的目標切點集中到一起進行處理,最後我們來總結一下,如果我們自己的專案要實現AOP的切面程式設計,應該要有幾步:

 1:我們要使用Aspect,肯定就需要有相應的jar包了,jar包的資源非常多,大家可以在網上隨便下載。

 2:要定義我們的切點,相當於本例中的DebugLog註解,它的@Target可以根據自己的需求來實現。

 3:定義自己的Aspect處理類,此類必須帶有@Aspect註解,需要在它裡面定義切點函式,定義切點的處理函式,也就是本例中Hugo類的logAndExecute方法了,Hugo類中的其他兩個方法enterMethod、exitMethod只是為了實現日誌列印的目的而寫的,大家可以根據自己的需求具體實現。

 4:完成了上面兩步,我們的專案中的Aspect框架就算搭建好了,下面就是新增切點了,也就是我們要在哪裡切入的問題,可以通過新增我們自定義的註釋來實現。

 5:我們的所有功能都完成了,最後還一定要注意編譯的方式要使用Aspect編譯器,如果大家使用javac編譯器的話,那生成的class檔案中根本沒有Aspect的程式碼,肯定也就無法實現我們的意圖了。

作者:付磊
原文連結:點選這裡

相關推薦

Android AOP介紹實現原理

深入理解Android之AOP 一、閒談AOP 大家都知道OOP,即ObjectOriented Programming,面向物件程式設計。而本文要介紹的是AOP。AOP是Aspect Oriented Programming的縮寫,中譯文為面向切向程式

【dubbo基礎】dubbo學習過程、使用經驗分享實現原理簡單介紹

multi spring配置 不同 影響 為什麽 exception 同事 sock services 一、前言 部門去年年中開始各種改造,第一步是模塊服務化,這邊初選dubbo試用在一些非重要模塊上,慢慢引入到一些稍微重要的功能上,半年時間,學習過程及線上使用遇到的些問

Android ListView動畫特效實現原理源代碼

stat 每一個 應該 所有 ner haar .get tde pri Android 動畫分三種,當中屬性動畫為我們最經常使用動畫,且能滿足項目中開發差點兒所有需求,google官方包支持3.0+。我們能夠引用三方包nineoldandr

dubbo學習過程、使用經驗分享實現原理簡單介紹

sum 使用 相同 應該 lib blog 組合 功能模塊 返回 一、前言 部門去年年中開始各種改造,第一步是模塊服務化,這邊初選dubbo試用在一些非重要模塊上,慢慢引入到一些稍微重要的功能上,半年時間,學習過程及線上使用遇到的些問題在此總結下。 整理這篇文章差不多花

AOP如何實現實現原理

概述: 最近在開發中遇到了一個剛好可以用AOP實現的例子,就順便研究了AOP的實現原理,把學習到的東西進行一個總結。文章中用到的程式語言為kotlin,需要的可以在IDEA中直接轉為java。 這篇文章將會按照如下目錄展開: AOP簡介 程式碼中實現舉例 AOP實現原理 部分原始碼解析

Spring中基於AspectJ的AOP切面程式設計介紹實現

簡介: AOP Aspect Oriented Programing 面向切面程式設計 AOP採取==橫向抽取==機制,取代了傳統==縱向繼承==體系重複性程式碼(效能監視、事務管理、安全檢查、快取) Spring中的Aop是純Java來實現的,使用==動態代理==的方式增強程

Android程式設計之Listener偵聽的N種寫法實現原理

寫下這個題目時突然想起魯迅筆下的孔乙已,茴香豆的幾種寫法,頗有些咬文嚼字的味道。雖然從事手機程式設計多年,但一直使用的是C和C++程式設計,由於安卓早期只支援JAVA開發,所以對於時下如火如荼的安卓系統,我一直觀之而未入之。現在由於工作需要開始研究安卓程式設計,由於以前主要使

Android中三級快取實現原理LruCache 原始碼分析

介紹 oom異常:大圖片導致 圖片的三級快取:記憶體、磁碟、網路 下面通過一張圖來了解下三級快取原理: 程式碼: public class Davince { //使用固定執行緒池優化 private static Exec

Android四種補間動畫介紹實現

一.Android的animation由四種類型組成:alpha、scale、translate、rotate alpha 漸變透明度動畫效果 scale 漸變尺寸伸縮動畫效果 translate 畫面轉換位置移

Android三種動畫實現原理使用

Android動畫目前分為三種:Frame Animation 幀動畫,通過順序播放一系列影象從而產生動畫效果,。圖片過多時容易造成OOM(Out Of Memory記憶體用完)異常。Tween Animation 補間動畫(又叫view動畫),是通過對場景裡的物件不斷做影象

Java JDK 動態代理(AOP)使用實現原理分析

/** * A factory function that generates, defines and returns the proxy class given * the ClassLoader and array of interfaces. */ privat

Java-JDK動態代理(AOP)使用實現原理分析

# Java-JDK動態代理(AOP)使用及實現原理分析 # 第一章:代理的介紹 介紹:我們需要掌握的程度 > 動態代理(理解) 基於反射機制 掌握的程度: 1.什麼是動態代理? 2.動態代理能夠做什麼? 後面我們在用Spirng和Mybatis的時候,要理解怎麼使用的. ## 1.什麼是代理

TCP/IP協議的三次握手實現原理

簡單 查找 32位 端口 包括 弱點 建立 成功 有效 TCP/IP是很多的不同的協議組成,實際上是一個協議組,TCP用戶數據報表協議(也稱作TCP傳輸控制協議,Transport Control Protocol。可靠的主機到主機層協議。這裏要先強調一下,傳輸控制協議是O

EJB2.0教程 詳解EJB技術實現原理

tee nsa 普通 事情 println 配置 ransac 教程 聲明 EJB是什麽呢?EJB是一個J2EE體系中的組件.再簡單的說它是一個能夠遠程調用的javaBean.它同普通的javaBean有兩點不同.第一點,就是遠程調用.第二點,就是事務的功能,我們在EJB中

ThreadLocal的使用場景實現原理

局部變量 運行 內部 然而 cal private 中間 pub new t 1. 什麽是ThreadLocal? 線程局部變量(通常,ThreadLocal變量是private static修飾的,此時ThreadLocal變量相當於成為了線程內部的全局變量) 2. 使用

Vlan與VTP的介紹工作原理

vlan vtp VLAN一個VLAN =一個廣播域 = 邏輯網段 (子網) 每個邏輯的VLAN就象一個獨立的物理橋交換機上的每一個端口都可以分配給不同的VLAN默認的情況下,所有的端口都屬於VLAN1(Cisco)每個邏輯的VLAN就象一個獨立的物理橋同一個VLAN可以跨越多個交換機主幹功能支持多個

spring aop配置實現

aop配置一、編寫接口代碼二、編寫接口代碼實現類三、編寫要實現切入的類四、編碼xml配置文件註:<aop:before> 為前切,<aop:after>為後切五、編寫測試代碼六、測試結果如下spring aop配置及實現

KVM虛擬化的四種簡單網絡模型介紹實現(一)

_for only 應該 code eth tun x86_64 信息 dock KVM中的四種簡單網絡模型,分別如下:1、隔離模型:虛擬機之間組建網絡,該模式無法與宿主機通信,無法與其他網絡通信,相當於虛擬機只是連接到一臺交換機上。2、路由模型:相當於虛擬機連接到一臺路由

KVM虛擬化的四種簡單網絡模型介紹實現(二)

str drive 51cto -c water -a return dfa 模型 接上篇,介紹NAT網絡模型和橋接模型。 三、NAT模型 NAT模型其實就是SNAT的實現,路由中虛擬機能將報文發送給外部主機,但是外部主機因找不到通往虛擬機的路由因而無法回應請求。但是外部

C++函式模板實現原理

    C++為我們提供了函式模板機制。所謂函式模板,實際上是建立一個通用函式,其函式型別和形參型別不具體指定,用一個虛擬的型別來代表。這個通用函式就稱為函式模板。     凡是函式體相同的函式都可以用這個模板來代替,不必定義多個函式,只需在模板中定義