主頁 > 後端開發 > 從零開始學習Java設計模式 | 結構型模式篇:裝飾者模式

從零開始學習Java設計模式 | 結構型模式篇:裝飾者模式

2021-08-20 07:24:30 後端開發

在本講,我們來學習一下結構型模式里面的第三個設計模式,即裝飾者模式,

概述

在學習裝飾者模式之前,我們先來看一個快餐店的例子,

快餐店有炒面、炒飯這些快餐,可以額外附加雞蛋、火腿、培根這些配菜,當然加配菜需要額外加錢,每個配菜的價錢通常不太一樣,那么這樣計算總價就會顯得比較麻煩,比如說客戶點一份炒面,他還要再加一個雞蛋和一根火腿,那么計算總價的時候就會很麻煩,

假設我們現在要設計這么一個計算總價的系統的話,那么用傳統的方式應該如何去做呢?是不是立馬想到了要用繼承的方式去做啊!這時,所設計出來的類圖就應該是下面這個樣子的,

在這里插入圖片描述

最上面的是快餐類,它里面有兩個成員變數,一個是price(即價格),一個是desc(即描述,例如炒飯的描述就是炒飯,炒面的描述就是炒面),當然還為它們提供了對應的getter和setter方法,此外,該快餐類里面還有一個cost方法,它就是用來計算快餐總價格的,

然后,快餐類又有兩個子類,一個是炒飯類(即FiredRice),一個是炒面類(即FiredNoodles),它倆都有各自對應的無參構造,除此之外,它倆都重寫了父類中的cost方法,以計算總價格,

由于對于炒飯和炒面來說,它們都可以加雞蛋或者培根,所以我就又為它們設計了不同的子類,對于炒飯類來說,它有兩個子類,分別是加雞蛋的炒飯類(即EggFriedRice)和加培根的炒飯類(即BaconFriedRice);對于炒面類來說,它也有兩個子類,分別是加雞蛋的炒面類(即EggFriedNoodles)和加培根的炒面類(即BaconFriedNoodles),

以上就是我們用傳統繼承的方式來實作咱們快餐店的案例,

那么使用繼承方式去實作的話,會存在一個什么樣的問題呢?使用繼承的方式所存在的問題:

  • 擴展性不好

    如果要再加一種配料(比如火腿腸),那么我們就會發現需要給FriedRice和FriedNoodles分別定義一個子類,如果要新增一個快餐品類(比如炒河粉)的話,那么就需要定義更多的子類了,為什么這么說呢?假設我們在以上類圖中新添加了一個炒河粉的子類,那么它肯定是要繼承自快餐類的,此時,它下面就要有三個子類了,分別是加雞蛋的炒河粉類、加培根的炒河粉類以及加火腿腸的炒河粉類,你會發現類爆炸的情況就出現了

  • 產生過多的子類

問題既然出現了,那么我們應該如何對上面的快餐店案例進行一個改進呢?此時,我們就可以使用裝飾者模式了,那裝飾者模式到底是什么呢?下面我們就來看看它的概念,

裝飾者模式是指在不改變現有物件結構的情況下,動態地給該物件增加一些職責(即增加其額外功能)的模式,

在以上快餐店案例中,對于炒飯來說,給其增加一個額外的職責,其實就是給其加一個雞蛋或者培根或者火腿腸,因此,對于該案例而言,我們就可以使用裝飾者模式了,

結構

裝飾者(Decorator)模式中的角色有如下四個:

  • 抽象構件(Component)角色:定義一個抽象介面以規范準備接收附加責任的物件,注意,此處的抽象介面既可以是介面也可以是抽象類,該角色對應以上快餐店案例中的快餐類
  • 具體構件(ConcreteComponent)角色:實作抽象構件,通過裝飾角色為其添加一些職責,例如,以上快餐店案例中的炒飯類、炒面類都屬于具體構建角色
  • 抽象裝飾(Decorator)角色:繼承或實作抽象構件,并包含具體構件的實體(也就是說將其聚合進來了),可以通過其子類擴展具體構件的功能,所以,裝飾者模式巧妙就巧妙在這個位置
  • 具體裝飾(ConcreteDecorator)角色:實作抽象裝飾的相關方法,并給具體構件物件添加附加的責任

裝飾者模式案例

上面我們學習了裝飾者模式的概念,以及知道了它里面所具有的角色,接下來,我們就使用裝飾者模式來對以上快餐店案例進行一個改進,以此體會裝飾者模式的精髓,

分析

咱們先看下下面的這張類圖,

在這里插入圖片描述

首先,我們應該明確要有一個快餐類,而且它還要是一個抽象類,它里面有兩個成員變數,一個是price(即價格),一個是desc(即描述),當然還為它們提供了對應的getter和setter方法,此外,該快餐類里面還有一個cost方法,它就是用來計算快餐總價格的,當然該方法是抽象的,需要由子類具體來實作,

然后,快餐類又有兩個子類,一個是炒飯類(即FiredRice),一個是炒面類(即FiredNoodles),它倆都有各自對應的無參構造,除此之外,它倆都重寫了父類中的cost方法,以計算總價格,

以上類圖的左半邊部分我們分析完了之后,再來分析一下右半邊部分,

可以看到有一個Garnish類,它是一個核心類,即裝飾者,作為裝飾者,首先它要去繼承快餐類(即FastFood),并又聚合該類的物件,這樣,我們就得為其提供一個有參構造和相應的getter、setter方法了,

接著,咱們的配料類,比如Egg、Bacon,就得提供對應的有參構造給父類(即Garnish)中的FastFood物件進行賦值了,并且還得重寫父類中的cost方法和getDesc方法,重寫父類中的cost方法好理解,就是為了計算總價,那為啥還要重寫父類中的getDesc方法呢?這是因為如果某個快餐加了雞蛋(或者培根)的話,那么它的描述肯定是不同了,所以我們就得重寫父類中的getDesc方法來獲取最終的一個描述了,

經過以上分析,大家一定要清楚,Garnish是最核心的一個類,它不僅繼承了FastFood類還聚合了FastFood類,

分析完了以后,接下來,我們就要開始撰寫代碼實作以上案例了,

實作

首先,打開咱們的maven工程,并在com.meimeixia.pattern包下新建一個子包,即decorator,也即裝飾者模式的具體代碼我們是放在了該包下,

然后,創建快餐類,這里我們命名為FastFood,注意,該類是一個抽象類,

package com.meimeixia.pattern.decorator;

/**
 * 快餐類(抽象構件角色)
 * @author liayun
 * @create 2021-07-31 13:02
 */
public abstract class FastFood {

    private float price; // 價格
    private String desc; // 描述

    public FastFood(float price, String desc) {
        this.price = price;
        this.desc = desc;
    }

    public FastFood() {

    }

    public float getPrice() {
        return price;
    }

    public void setPrice(float price) {
        this.price = price;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    /*
     * 計算總價,注意,該方法是一個抽象的方法,這是因為只有我們知道了具體的快餐之后,才能計算出來它的價格,
     * 例如,如果客戶點的是炒飯,而一碗炒飯又是10塊錢,那么最侄訓傳的就是10塊錢
     */
    public abstract float cost();

}

接著,創建兩個快餐類的子類,一個是炒飯類,這里我們命名為FriedRice,

package com.meimeixia.pattern.decorator;

/**
 * 炒飯類(具體構件角色)
 * @author liayun
 * @create 2021-07-31 13:10
 */
public class FriedRice extends FastFood {

    /*
     * 在FriedRice類中,我們只需要給它提供一個無參的構造方法就可以了,但是我們得通過該無參構造給父類中的兩個成員變數進行賦值,
     * 如果客戶選擇的是炒飯,而炒飯的價格又是固定的,比如10塊錢,那么代碼就應該向下面這樣寫,
     */
    public FriedRice() {
        super(10, "炒飯");
    }

    @Override
    public float cost() {
        return getPrice(); // 由于我們剛才已經定義好了炒飯的價格是10塊錢,所以此處我們直接呼叫
                           // 父類中的getPrice方法就能獲取到價格了
    }

}

現在,如果我們想要點一份炒飯,那么是不是只需要創建FriedRice類的物件就可以了啦!而且這樣就會自動將炒飯的價格設定為10塊錢,描述那當然就是炒飯了啊!最終,我們去計算一份炒飯的餐費的話,那就是10塊錢了,這是非常合情合理的,

FriedRice類還要一個子類,就是炒面類,這里我們命名為FriedNoodles,

package com.meimeixia.pattern.decorator;

/**
 * 炒面類(具體構件角色)
 * @author liayun
 * @create 2021-07-31 13:17
 */
public class FriedNoodles extends FastFood {

    /*
     * 在FriedNoodles類中,我們只需要給它提供一個無參的構造方法就可以了,但是我們得通過該無參構造給父類中的兩個成員變數進行賦值,
     * 如果客戶選擇的是炒面,而炒面的價格又是固定的,比如12塊錢,那么代碼就應該像下面這樣寫,
     */
    public FriedNoodles() {
        super(12, "炒面");
    }

    @Override
    public float cost() {
        return getPrice(); // 由于我們剛才已經定義好了炒面的價格是12塊錢,所以此處我們直接呼叫
                           // 父類中的getPrice方法就能獲取到價格了
    }

}

緊接著,創建最核心的類,即Garnish,它就是裝飾者類,對于該裝飾者類,大家一定要注意它設計的一個原則,就是它得去繼承FastFood類,雖說是去繼承,但是在這里我們就不重寫它里面的方法了,注意了,我們應該將該裝飾者類定義成抽象的,為什么呢?因為快餐具體要加哪種配料,我們是不明確的,不明確的話,那么總價就無法進行計算了,所以我們就需要把該裝飾者類定義成抽象類了,

你覺得該裝飾者類屬于裝飾者模式里面的哪種角色呢?很明顯,它屬于抽象裝飾角色,注意了,上面我們還沒說完Garnish類設計的原則呢,下面我們接著來說,

Garnish類除了要去繼承FastFood類之外,還得聚合FastFood類的物件,所以我們就得在Garnish類的成員位置宣告FastFood類的變數了,這是裝飾者模式的一個顯著特點,記住,定義完FastFood型別的成員變數之后,還得為其對應的getter和setter方法,

這樣,Garnish類的代碼就呼之欲出了,

package com.meimeixia.pattern.decorator;

/**
 * 裝飾者類(抽象裝飾者角色)
 * @author liayun
 * @create 2021-07-31 13:27
 */
public abstract class Garnish extends FastFood {

    // 宣告快餐類的變數
    private FastFood fastFood;

    public FastFood getFastFood() {
        return fastFood;
    }

    public void setFastFood(FastFood fastFood) {
        this.fastFood = fastFood;
    }

    /*
     * 在Garnish類中,我們還得提供如下有參構造,價格與描述這兩屬性是直接呼叫父類中的方法來進行設定的,
     * 至于FastFood型別的屬性,懂得都懂!!!
     *
     * 注意,后面的兩個引數所代表的意思,
     *      float price:配料(例如雞蛋)的價格,例如,一個炒雞蛋是1塊錢
     *      String desc:配料(例如雞蛋)的描述,例如,雞蛋的描述肯定就是雞蛋了
     */
    public Garnish(FastFood fastFood, float price, String desc) {
        super(price, desc);
        this.fastFood = fastFood;
    }

}

裝飾者類創建完畢之后,接下來,我們就得開始創建配料類了,第一個配料類是雞蛋類,即Egg,注意了,它得繼承Garnish裝飾者類,

繼承完了之后,我們得來思考一下,對于該雞蛋類來說,我們肯定是要提供構造方法的,那么是提供有參的構造方法還是無參的構造方法呢?很明顯是提供有參的構造方法,因為我們還得給父類中的快餐類成員變數進行賦值呢!你不可能只要配料而不要具體的快餐吧!比如說你就去快餐店只點兩個炒雞蛋,而不來一份炒飯,當然了,你非得干吃兩個炒雞蛋那也不是不可以,只是這種情況很少很少,

除此之外,在該雞蛋類中,我們還得重寫父類中的cost方法以便計算快餐加配料之后的總價,如何來重寫父類中的cost方法呢?很簡單,雞蛋的價格加上快餐的價格就計算出來了,

重寫完父類中的cost方法之后,注意了,我們還得重寫父類中的getDesc方法,重寫也很簡單,就是雞蛋的描述拼接上快餐的描述就行了,

這樣,第一個配料類(即Egg)的代碼就呼之欲出了,

package com.meimeixia.pattern.decorator;

/**
 * 雞蛋類(具體的裝飾者角色)
 * @author liayun
 * @create 2021-07-31 13:40
 */
public class Egg extends Garnish {

    public Egg(FastFood fastFood) {
        super(fastFood, 1, "雞蛋");
    }

    @Override
    public float cost() {
        // 計算價格
        return getPrice() + getFastFood().cost();
    }

    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }
}

同理,第二個配料類(即Bacon)的代碼就不難寫出了,

package com.meimeixia.pattern.decorator;

/**
 * 培根類(具體的裝飾者角色)
 * @author liayun
 * @create 2021-07-31 13:40
 */
public class Bacon extends Garnish {

    public Bacon(FastFood fastFood) {
        super(fastFood, 2, "培根");
    }

    @Override
    public float cost() {
        // 計算價格
        return getPrice() + getFastFood().cost();
    }

    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }
}

最后,我們就要撰寫一個客戶端類來測驗一下了,

package com.meimeixia.pattern.decorator;

/**
 * @author liayun
 * @create 2021-07-31 15:32
 */
public class Client {
    public static void main(String[] args) {
        // 點一份炒飯
        FastFood food = new FriedRice();
        // 列印炒飯的價格與描述
        System.out.println(food.getDesc() + " " + food.cost() + "元");
    }
}

此時,運行以上客戶端類,如下圖所示,可以看到列印的炒飯的價格確實是10塊錢,因為我們之前對炒飯的價格設定就是10塊錢,

在這里插入圖片描述

如果我們此時想在上面的炒飯里面加上一個炒雞蛋,那么應該怎么去做呢?測驗代碼是不是應該像下面這樣啊!

package com.meimeixia.pattern.decorator;

/**
 * @author liayun
 * @create 2021-07-31 15:32
 */
public class Client {
    public static void main(String[] args) {
        // 點一份炒飯
        FastFood food = new FriedRice();
        // 列印炒飯的價格與描述
        System.out.println(food.getDesc() + " " + food.cost() + "元");
        System.out.println("===================");
        // 在上面的炒飯中加一個雞蛋
        food = new Egg(food);
        // 列印炒飯加上雞蛋之后的價格與描述
        System.out.println(food.getDesc() + " " + food.cost() + "元");
    }
}

再運行以上客戶端類,如下圖所示,可以看到炒飯加了一個雞蛋之后,總價確實變成了11塊錢,

在這里插入圖片描述

現在我還能在以上雞蛋炒飯里面再加上一個炒雞蛋嗎?顯然是可以的啊!測驗代碼如下所示,

package com.meimeixia.pattern.decorator;

/**
 * @author liayun
 * @create 2021-07-31 15:32
 */
public class Client {
    public static void main(String[] args) {
        // 點一份炒飯
        FastFood food = new FriedRice();
        // 列印炒飯的價格與描述
        System.out.println(food.getDesc() + " " + food.cost() + "元");
        System.out.println("===================");
        // 在上面的炒飯中加一個雞蛋
        food = new Egg(food);
        // 列印炒飯加上雞蛋之后的價格與描述
        System.out.println(food.getDesc() + " " + food.cost() + "元");
        System.out.println("===================");
        // 再加一個雞蛋
        food = new Egg(food);
        System.out.println(food.getDesc() + " " + food.cost() + "元");
    }
}

再運行以上客戶端類,如下圖所示,可以看到雞蛋炒飯加了一個炒雞蛋之后,總價確實變成了12塊錢,

在這里插入圖片描述

那么我們還能再給以上雞蛋雞蛋炒飯加一根培根嗎?顯然是可以的啊!測驗代碼如下所示,

package com.meimeixia.pattern.decorator;

/**
 * @author liayun
 * @create 2021-07-31 15:32
 */
public class Client {
    public static void main(String[] args) {
        // 點一份炒飯
        FastFood food = new FriedRice();
        // 列印炒飯的價格與描述
        System.out.println(food.getDesc() + " " + food.cost() + "元");
        System.out.println("===================");
        // 在上面的炒飯中加一個雞蛋
        food = new Egg(food);
        // 列印炒飯加上雞蛋之后的價格與描述
        System.out.println(food.getDesc() + " " + food.cost() + "元");
        System.out.println("===================");
        // 再加一個雞蛋
        food = new Egg(food);
        System.out.println(food.getDesc() + " " + food.cost() + "元");
        System.out.println("===================");
        // 再加一個培根
        food = new Bacon(food);
        System.out.println(food.getDesc() + " " + food.cost() + "元");
    }
}

再運行以上客戶端類,如下圖所示,可以看到雞蛋雞蛋炒飯加了一根培根之后,總價確實變成了14塊錢,

在這里插入圖片描述

當然,對于炒面,你也可以參照以上代碼再去進行測驗,只不過這里我就略過了,

最終,你會發現使用裝飾者模式設計出來的系統會特別特別靈活,如果此時我們想要再去新增一個配料的話,例如火腿腸,那么我們只需要再去定義一個火腿腸類,然后讓它去繼承裝飾者類就可以了,

裝飾者模式的好處以及使用場景

好處

裝飾者模式的好處,我總結出來了如下兩個,

  1. 裝飾者模式可以帶來比繼承更加靈活性的擴展功能(這是因為裝飾者模式本身就是在原有的基礎上進行了一個擴展),使用更加方便,可以通過組合不同的裝飾者物件來獲取具有不同行為狀態的多樣化的結果(比如說,你可以在炒飯的基礎上加雞蛋、加培根),裝飾者模式比繼承更具良好的擴展性,完美的遵循開閉原則,繼承是靜態的附加責任,裝飾者則是動態的附加責任

  2. 裝飾者類和被裝飾者類可以獨立發展,不會相互耦合,裝飾者模式是繼承的一個替代模式,裝飾者模式可以動態擴展一個實作類的功能,

    上面這句話說的是啥意思啊?我用上面的快餐店案例來給大家詳細解釋一下,如果現在我們要再添加一種品類的快餐的話,例如炒河粉,那么是不是只需要再去定義一個炒河粉類,然后讓它直接去繼承FastFood類就可以了啊?而如果此時我們想要再去添加一個配料的話,例如火腿腸,那么是不是只需要再去定義一個火腿腸類,然后讓它去繼承裝飾者類就可以了啊?這樣,裝飾者類和被裝飾者類不就可以獨立發展了嘛!而且它們還不會相互耦合

使用場景

裝飾者模式的使用場景,我總結出來了如下三個,

  1. 當不能采用繼承的方式對系統進行擴充或者采用繼承不利于系統擴展和維護時,我們就可以使用裝飾者模式了,

    不能采用繼承的情況主要有兩類:

    • 第一類是系統中存在大量獨立的擴展(比如說配料或者快餐的品種),為支持每一種組合將產生大量的子類,使得子類數目呈爆炸性增長,而定義太多的子類會讓系統變得更加的復雜
    • 第二類是因為類定義不能繼承(如final類)
  2. 在不影響其他物件的情況下,以動態、透明的方式給單個物件添加職責

  3. 當物件的功能要求可以動態地添加,也可以再動態地撤銷時,

    哎,動態地撤銷又該怎么去理解呢?我用上面的快餐店案例再來給大家詳細解釋一下,快餐店里面的雞蛋賣完了之后,我們只需要動態地把Egg這個子類移除掉就可以了;又買了一些雞蛋之后,再將該子類添加上就行,這就是所謂的動態地添加和撤銷

裝飾者模式在JDK原始碼中的應用

接下來,我們來看下裝飾者模式在JDK原始碼里面是如何應用的?

IO流中的包裝類使用到了裝飾者模式,而哪些屬于包裝類呢?像BufferedInputStream、BufferedOutputStream、BufferedReader以及BufferedWriter等這些都屬于包裝類,也就是說這些類都用到了裝飾者模式,下面我們就以BufferedWriter舉例來說明一下,

我們不妨先看看如何使用BufferedWriter類吧!

public class Demo {
    public static void main(String[] args) throws Exception{
        // 創建BufferedWriter物件
        // 創建FileWriter物件
        FileWriter fw = new FileWriter("C:\\Users\\liayun\\Desktop\\a.txt");
        BufferedWriter bw = new BufferedWriter(fw);

        //寫資料
        bw.write("Hello Buffered");

        bw.close();
    }
}

以上代碼很簡單,我們是要去創建BufferedWriter物件的,但是其構造方法里面需要一個Writer的子實作類物件,所以我們得提前創建一個FileWriter物件,并把它作為引數進行一個傳遞,然后,我們就能使用緩沖流里面的write方法進行資料的一個寫出操作了,最后就是釋放資源,

當然以上代碼我在這里就不給大家運行演示了,如果你要是有興趣的話,不妨私下自己去跑一跑,

簡單使用了一下BufferedWriter類之后,你會發現它使用起來感覺確實像是裝飾者模式,那么到底是不是呢?我們來看一下下面這張類圖,

在這里插入圖片描述

可以看到,頂層父類是Writer,它是字符輸出流的頂層父類,并且它還有幾個子類,一個子類是InputStreamWriter(該類下面又有一個子類,即FileWriter),一個子類是BufferedWriter,

你注意看,BufferedWriter類同時又聚合了Writer類,也就是說BufferedWriter類不僅繼承自Writer類,并且還聚合了Writer類,這很明顯就是裝飾者模式,裝飾者模式的巧妙之處就在于這,

至此,通過以上類圖,我們就分析出了BufferedWriter類確實是用到了裝飾者模式,至于其他的幾個類,大家有興趣的話,可以自己去查看一下它們的源代碼,看一下它們是不是也用到了裝飾者模式,只是我在這里就不贅述了,

最后,我做一個小結,BufferedWriter使用裝飾者模式對Writer子實作類進行了增強,即添加了緩沖區,提高了寫資料的效率,

裝飾者模式和靜態代理的區別

接下來,我們來說說靜態代理和裝飾者模式的一個區別,因為它倆很像很像,不熟悉它倆之間區別的人很容易把它們搞混到一塊去,

相同點

裝飾者模式和靜態代理的相同點:

  1. 都要實作與目標類相同的業務介面,

    這是說的啥意思呢?我們不妨往上看一下上面的快餐點案例的類圖,可以看到都得去繼承(或者實作)FastFood抽象類,是不是啊?

  2. 在兩個類中都要宣告目標物件,也就是說,都要把你繼承的那個類的子類物件給聚合進來

  3. 都可以在不修改目標類的前提下增強目標方法

以上幾點看下來,靜態代理和裝飾者模式是不是特像啊!

不同點

裝飾者模式和靜態代理的不同點:

  1. 目的不同,裝飾者模式是為了增強目標物件,而靜態代理是為了保護和隱藏目標物件,

    這話什么意思呢?回顧一下使用裝飾者模式實作的快餐店案例的代碼,在裝飾者類(即Garnish)中,是不是宣告了一個FastFood型別的成員變數啊!你注意了,在這兒我們并沒有對該成員變數進行賦值,那么它應該由誰來賦值呢?誰使用,誰就對該成員變數進行賦值;而如果是靜態代理的話,那么在這一塊就是直接創建一個FastFood物件,并將其賦值給該成員變數了,這就相當于是保護和隱藏了目標物件

  2. 獲取目標物件構建的地方不同,裝飾者模式是由外界傳遞進來的,可以通過構造方法傳遞,當然我們也可以通過setter方法進行一個傳遞;而靜態代理是在代理類內部創建的,也就是說如果代理類是Garnish這個類的話,那么在成員變數處就直接創建了FastFood物件,這樣做的目的就是為了隱藏目標物件

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/294934.html

標籤:java

上一篇:【爆肝一周】面試10多家中大廠后的萬字總結——??JavaWeb篇??(建議收藏)

下一篇:??大三的時候,看了這些Java面試題【附答案】,我進華為了![建議收藏]??

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more