主頁 > 後端開發 > 【JAVA SE】三萬字終極魔典 面向物件編程深度講解(包+繼承+多型+抽象類+介面 全面剖析)

【JAVA SE】三萬字終極魔典 面向物件編程深度講解(包+繼承+多型+抽象類+介面 全面剖析)

2022-01-17 19:40:23 後端開發

本章目錄

  • 溫馨提示
  • 開篇介紹( 知識點比較多 務必耐心看完!)
  • 本章重點
  • 正文開始
    • 1. 包
      • 1.1 匯入包中的類
      • 1.2 靜態匯入
      • 1.3 將類放到包中
      • 1.4 包的訪問權限控制
      • 1.5 常見的系統包
    • 2. 繼承
      • 2.1 了解繼承
      • 2.2 語法規則(比較枯燥)
      • 2.3 子類的構造方法
      • 2.4 super 和 this 的區別
      • 2.5 訪問權限
      • 2.6 更復雜的繼承關系
      • 2.7 final 關鍵字
    • 3. 多型
      • 3.1 向上轉型
        • 知識點補充:父類參考訪問成員
      • 3.2 動態系結
        • 知識點補充1:在構造方法中呼叫重寫的方法
        • 知識點補充2:靜態系結
      • 3.3 方法重寫
      • 3.4 重寫和多載的區別(重新整理)
      • 3.5 向下轉型
      • 3.6 理解多型
    • 4. 抽象類
      • 4.1 了解抽象類
      • 4.2 語法規則
      • 4.3 抽象類的作用
    • 5. 介面
      • 5.1 了解介面及簡單語法規則
      • 5.2 實作多個介面及其他語法規則
      • 5.3 介面之間的繼承
      • 5.4 介面使用實體
        • Comparable介面
        • Comparator介面
        • Cloneable介面及深拷貝和淺拷貝
      • 5.5 抽象類和介面的區別
  • 全文結束

溫馨提示

大家好我是Cbiltps,在我的博客中如果有難以理解的句意難以用文字表達的重點,我會有配圖,所以我的博客配圖非常重要!!!

而且很多知識在代碼的注釋里,所以代碼注釋也非常重要!!!

這篇文章前后邏輯順序非常重要,一定要從前往后看,慢慢看!!!

開篇介紹( 知識點比較多 務必耐心看完!)

等后面我會寫關于類和物件的博客,其實在學習本篇博客之前一定要類和物件有一定的了解!就是大家先學習類和物件的博客,然后再看這篇面向物件文章,你就會有一個階梯式的理解

緊接著,在這篇文章的尾部,我會展開一個簡單且知識點全面的一個關于面向物件的訓練(最后面直接貼鏈接),大家拭目以待吧!學習完后你就會對面向物件有一個初步的認識

還有一點:這篇文章橫向拓展比較多,全程三萬多字!大家在學習的時候會看到豐富的知識點,覆寫面也比較全!

那么,今天這篇博客的知識點在小米阿里巴巴百度VIVO騰訊攜程貝殼美團頭條網易京東滴滴等公司常考!!!

本章重點

  • 繼承
  • 多型
  • 抽象類
  • 介面

正文開始


1. 包


(package) 是組織類的一種方式,使用包的主要目的是保證類的唯一性

上面的話晦澀難懂,我們先不要糾結這個,直接舉例子演示(看下面)!

1.1 匯入包中的類

比如說列印陣列的時候就匯入了包中的類:

package com.company;

import java.util.Arrays;//呼叫了util這個包中的Arrays這個類

public class Main {

    public static void main(String[] args) {
	    int[] array = {1,2,3,4,5};
        System.out.println(Arrays.toString(array));//用字串的方式列印陣列
    }
}

注意一個問題: toString這個方法是由類名(Arrays)呼叫的,所以這個方法就是一個靜態方法按住CTRL后滑鼠點進去,進入Arrays.java檔案,如下圖)!
在這里插入圖片描述
所以所有通過類名直接呼叫的方法就一定是靜態方法,我們不用管這個方法是怎么執行的,這個是官方寫好的,直接呼叫即可!

那么,它在哪個包底下呢?

在上面打開檔案(Arrars.java)的最前面就可以看到的,如下
在這里插入圖片描述
我們可以找到這個檔案(Arrars.java)的路徑:
在這里插入圖片描述
關于上面的package關鍵字是什么,一會說!而且再看下面一點點(Arrars.java中)還有import關鍵字
在這里插入圖片描述
然后提出疑問:importpackage有什么區別?

  • package 是你運行程式后生成的.class檔案全部放在了你所定義的包中,便于以后呼叫管理,
  • import 則是在撰寫程式的時候需要呼叫某個包中的類,

也就是說,如果要用到一些Java類別庫里面代碼的時候,我們都需要import來匯入的!

然后再舉一個匯入例子:

//匯入方式1
package com.company;

import java.util.Date;//在這里匯入

public class Main {

    public static void main(String[] args) {
        Date date = new Date();
    }
}
//匯入方式2
package com.company;

public class Main {

    public static void main(String[] args) {
        java.util.Date date = new java.util.Date();//這匯入也可以,但是比較麻煩,推薦方式1
    }
}

記住:只能匯入一個具體的類,不能匯入一個具體的包
在這里插入圖片描述

然后緊接著下一個問題:我們看到有人是這樣匯入包的,這里的*是什么意思?

import java.util.*;
  • * 是一個通配符:意思是匯入這個包底下所有的類

疑問:util下面有很多的類,難道一下子全部匯入了嗎?

不是的,Java處理的時候,需要誰,它才會拿誰

C語言里面,通過include關鍵字匯入之后,會把頭檔案里面的內容全部拿過來

但是,這樣子(import java.util.*;)匯入的范圍太廣了,你有可能把握不住,有時候會有沖突(如下),建議匯入具體的類名
在這里插入圖片描述
但是,假設你想用另一個包底下的Date,就識別不了了(看下圖):
在這里插入圖片描述
解決方法就是:使用完整的類名匯入
在這里插入圖片描述

//代碼組織如下:
package com.company;

import java.util.*;
import java.sql.*;
//以上兩個包中都有Date類,為了避免沖突,使用如下操作

public class Main {

    public static void main(String[] args) {
        java.util.Date date = new java.util.Date();//使用完整的類名匯入
    }
}

1.2 靜態匯入

一般情況下,靜態匯入用的非常的少,了解即可!

package com.company;

import static java.lang.System.*;
import static java.lang.Math.*;

public class Main {

    public static void main(String[] args) {
        out.println("123");//靜態匯入寫起來方便
        out.println(max(12, 23));//但是,不提倡這樣寫,不方便閱讀,稀奇古怪
    }
}

1.3 將類放到包中

廢話不多說,直接上步驟

  1. 新建一個包
    在這里插入圖片描述
  2. 創建類
    在這里插入圖片描述

同時可以看到磁盤上的目錄結構已經被 IDEA 自動創建出來了
在這里插入圖片描述

基本規則(注意以下幾點):

  • 在檔案的最上方加上一個 package 陳述句指定該代碼在哪個包中
  • 包名必須是小寫的
  • 包名需要盡量指定成唯一的名字, 通常會用公司的域名的顛倒形式(例如:com.xxxxx.www)
  • 包名要和代碼路徑相匹配,例如創建com.cbiltps的包, 那么會存在一個對應的路徑com/cbiltps來存盤代碼
  • 如果一個類沒有 package 陳述句, 則該類被放到一個默認包中

經過上面的步驟后,回傳第一節的第一句話 (package) 是組織類的一種方式,使用包的主要目的是保證類的唯一性

如何保證類的唯一性呢?
其實就是磁盤上的同一目錄下(同一個包下)只能有一個同名的檔案(類),并且包名不一樣了,路徑也不一樣,也就互相不干擾了!

1.4 包的訪問權限控制

包訪問權限:顧名思義,就是只能在當前包中使用

舉個例子:當你的成員變數不加任何的訪問修飾限定詞的時候,就是包訪問權限

由于在不同的包中展示,不方便直接代碼展示,所以就截圖展示了(比較亂,見諒!)
在這里插入圖片描述
由此可見,cbiltps包無法訪問company包中的val值,所以包訪問權限只能在當前包中使用

1.5 常見的系統包

  1. java.lang:系統常用基礎類(String、Object)
    此包從JDK1.1后自動匯入,不需要import進行匯入
  2. java.lang.reflect:java反射編程包,
  3. java.net:進行網路編程開發包,
  4. java.sql:進行資料庫開發的支持包,
  5. java.util:是java提供的工具程式包,(集合類等) 非常重要
  6. java.io:I/O編程開發包,

2. 繼承


在學習本小節之前先來復習一下知識點:面向物件的基本特征(共四點 先說兩點)

  • 封裝:不必要公開的資料成員和方法,使用private關鍵字修飾(為了安全性!
  • 繼承:對共性的抽取,使用extends進行處理(代碼可以重復使用!

2.1 了解繼承

代碼中創建的類, 主要是為了抽象現實中的一些事物(包含屬性和方法)

有的時候客觀事物之間就存在一些關聯關系, 那么在表示成類和物件的時候也會存在一定的關聯

例如, 設計一個類表示動物(直接上代碼):

class Animal {
    //動物的名字和年齡都是共有的!
    public String name;
    public int age;

    //包括吃也是共有的行為!
    public void eat() {
        System.out.println(name + "eat()");
    }
}

class Dog extends Animal {
    //抽取完后大大減少了代碼量!
}

class Bird extends Animal {
    public String wing;

    public void fly() {
        System.out.println(name+"fly()" + age);
    }
}

此時,Animal 這樣被繼承的類, 我們稱為父類基類超類,對于像 DogBird 這樣的類,我們稱為子類派生類

從邏輯上講,DogBird 都是一種 Animal (is - a 語意);

和現實中的兒子繼承父親的財產類似,子類也會繼承父類的欄位和方法,以達到代碼重用的效果,
在這里插入圖片描述
extends 英文原意指 “擴展”,而我們所寫的類的繼承,也可以理解成基于父類進行代碼上的 “擴展”;

例如我們寫的 Bird 類, 就是在 Animal 的基礎上擴展出了 fly 方法,

2.2 語法規則(比較枯燥)

基本語法:

class 子類 extends 父類 {
}

注意

  • 使用 extends 指定父類

  • Java 中一個子類只能繼承一個父類 (而C++/Python等語言支持多繼承)
    在這里插入圖片描述

  • 子類會繼承父類的所有 public 的欄位和方法

  • 對于父類的 private 的欄位和方法,子類中是無法訪問
    在這里插入圖片描述

  • 子類的實體中,也包含著父類的實體,可以使用 super 關鍵字得到父類實體的參考

上面簡單的總結后看另外一個問題:
在這里插入圖片描述
得出結論:子類和父類欄位同名的情況下優先訪問的是子類的 !

而它的記憶體圖是這樣子的:
在這里插入圖片描述

2.3 子類的構造方法

看一個問題:根據上面的表示動物類的代碼中添加父類的構造方法后為什么會產生下面的錯誤?
在這里插入圖片描述
根據上面的報錯,我們得到下面的結論:

  • 父類構造方法不能被子類繼承
  • 子類的構造方法中必須要呼叫父類的構造方法

而且注意:呼叫父類構造方法的時候

  • 子類通過 super(引數串列)呼叫父類構造方法,呼叫super的陳述句必須放在子類構造方法的第一行
  • 若子類構造方法中沒有顯示呼叫父類構造方法,則系統默認呼叫父類無參的構造方法;若父類中沒有定義無引數的構造方法,編譯出錯

所以,按照下面的樣子改即可:
在這里插入圖片描述
上面的知識點明白之后,我們補充完主類后,畫一下記憶體圖加強理解):

//主類的代碼如下:
public class TestDemo {
    public static void main(String[] args) {
        Dog dog = new Dog("哈士奇",19);
        System.out.println(dog.name);
        dog.eat();

        Bird bird = new Bird("喜鵲",18,"我要的飛翔");
        System.out.println(bird.name);
        bird.eat();
        bird.fly();
    }
}

在這里插入圖片描述

2.4 super 和 this 的區別

在之前的學習中已經遇見 superthis 兩個關鍵字,博主根據自身所學及博客參考做出如下總結:

super: 可以理解為父類物件的參考(是依賴物件的),不能出現在靜態環境(包括:static變數,static方法,static陳述句塊)中因為 static 不依賴物件)!

  • super(); //呼叫父類的構造方法
  • super.func(); //呼叫父類的普通方法
  • super.data; //呼叫父類的成員屬性

this: 可以理解為指向本物件的指標,它代表當前物件名(在程式中易產生二義性之處,應使用 this指明當前物件;如果函式的形參類中的成員資料同名,這時需用 this指明成員變數名)!

  • this(); //呼叫本類中另一種形成的構造方法

注意點與區別總結:

  • super();this();區別是:super();從子類中呼叫父類的構造方法,this();在同一類內呼叫其它方法
  • super();this();均需放在構造方法內第一行
  • 有時候 thissuper 不能同時出現在一個建構式里面,因為 this 必然會呼叫其它的建構式,其它的建構式必然也會有 super 陳述句的存在,所以在同一個建構式里面有相同的陳述句,就失去了陳述句的意義,編譯器也不會通過
  • this();super();都指的是物件,所以,均不可以在 static環境中使用(包括:static變數,static方法,static陳述句塊)

在這里插入圖片描述

2.5 訪問權限

Java中對于欄位方法共有四種訪問權限

  • private: 類內部能訪問, 類外部不能訪問
  • 默認(也叫包訪問權限): 類內部能訪問, 同一個包中的類可以訪問, 其他類不能訪問
  • protected: 類內部能訪問, 子類和同一個包中的類可以訪問, 其他類不能訪問
  • public : 類內部和類的呼叫者都能訪問

重要的是下面的范圍圖:
在這里插入圖片描述

private關鍵字(補充的一點):

這里有一個問題

父類的 private 修飾的成員變數是否被繼承了?

這個問題的答案有點模糊,有的書上說是繼承了的;

但有的書上說不是,建議我們采用沒有沒繼承的答案!

原因看下面:

//父類
class A {
    private int a;//使用private修飾
}
//子類 這段代碼是錯
class B extends A { //繼承
    public void func() {
        System.out.println(this.a);//這里無法訪問就說是沒有沒繼承的!
    }
}

這個知識點先就寫到這里,后期有需要的話經過百度之后有可能會補充!

大家也可以在評論區自由發揮!

protected關鍵字:

剛才我們發現,如果把欄位設為 private,子類不能訪問;

但是設成 public,又違背了我們 “封裝” 的初衷;

兩全其美的辦法就是 protected 關鍵字,

  • 對于類的呼叫者來說,protected 修飾的欄位和方法是不能訪問
  • 對于類的 子類同一個包的其他類 來說,protected 修飾的欄位和方法是可以訪問

什么時候下用哪一種呢?

我們希望類要盡量做到 “封裝”,即隱藏內部實作細節,只暴露出必要的資訊給類的呼叫者,

因此我們在使用的時候應該盡可能的使用比較嚴格的訪問權限,

例如如果一個方法能用 private,就盡量不要用 public

另外,還有一種簡單粗暴的做法:將所有的欄位設為 private, 將所有的方法設為public

不過這種方式屬于是對訪問權限的濫用,還是更希望同學們能寫代碼的時候認真思考,該類提供的欄位方法到底給 “誰” 使用(是類內部自己用,還是類的呼叫者使用,還是子類使用),

2.6 更復雜的繼承關系

這里有個不成文的規則:繼承層次最好不要超過三個!

時刻牢記,我們寫的類是現實事物的抽象,而我們真正在公司中所遇到的專案往往業務比較復雜,可能會涉及到一
系列復雜的概念,都需要我們使用代碼來表示,所以我們真實專案中所寫的類也會有很多,類之間的關系也會更加
復雜,

但是即使如此,我們并不希望類之間的繼承層次太復雜,一般我們不希望出現超過三層的繼承關系,如果繼承層
次太多,就需要考慮對代碼進行重構了,

如果想從語法上進行限制繼承,就可以使用 final 關鍵字 !

2.7 final 關鍵字

曾經我們學習過 final 關鍵字,修飾一個變數或者欄位的時候,表示常量 (不能修改)

final int a = 10;
a = 20; //編譯出錯

final 關鍵字也能修飾(叫做密封類),此時表示被修飾的類就不能被繼承 !

final 關鍵字的功能是限制類被繼承,“限制” 這件事情意味著 “不靈活”,

在編程中,靈活往往不見得是一件好事,靈活可能意味著更容易出錯

而我們常見的String類就是final修飾的:
在這里插入圖片描述


3. 多型


從字面上理解,就是一種事物多種形態,

但是,面試官問的時候不能這樣回答!

了解多型需要一個程序!

3.1 向上轉型

根據上面的繼承關系,我們進行探討:

public static void main(String[] args) {
        /*Dog dog = new Dog("旺財",20);
        Animal animal = dog;*/
        
        //把上面的代碼簡化一下
        Animal animal = new Dog("旺財",23);//向上轉型   
        //其實就是:父類參考 參考 子類物件
    }

知識點補充:父類參考訪問成員

學到這里我在添加一個知識點(這一個知識點作為 繼承訪問的補充),

然后我們再次重新展示一下代碼(為了方便我直接把知識點寫寫進了代碼里,請大家注意查收):

//Animal類
class Animal {
    public String name = "動物";
    public int age;
    protected int count;

    public Animal(String name,int age) {
        this.name = name;
        this.age = age;
    }

    public void eat() {
        System.out.println(name+" eat()");
    }
}
//Bird類 繼承于 Animal
lass Bird extends Animal {
    public String wing;
    public String name = "鳥類";

    public Bird(String name, int age, String wing) {
        super(name, age);
        this.wing = wing;
    }

    public void fly() {
        System.out.println(super.name + "fly()");
    }
}
//主類的主方法
public static void main(String[] args) {

        Animal animal2 = new Bird("wuya",12,"wuya fly!");//這里發生向上轉型
        animal2.eat();//可以呼叫eat方法

        System.out.println(animal2.name);//這里其實訪問的是父類的name

//        注意了:重點知識在這里!!!
//        System.out.println(animal2.wing); 無法訪問的
//        因為animal的型別是Animal型別
//        意思就是:通過父類參考,只能訪問父類自己的成員!

    }

什么情況下會發生向上轉型?

  1. 直接賦值(就是上面的情況)
public static void main(String[] args) {
        /*Dog dog = new Dog("旺財",20);
        Animal animal = dog;*/
        
        //把上面的代碼簡化一下
        Animal animal = new Dog("旺財",23);//向上轉型   
        //其實就是:父類參考 參考 子類物件
    }
  1. 作為函式的引數
public class TestDemo2 {

    public static void func(Animal animals) {

    }

    public static void main(String[] args) {

        Dog dog = new Dog("金毛",20);
        func(dog);//在這里發生向上轉型
    }
}
  1. 作為方法的回傳值
 public static Animal fun2(Animal animalss) {
        Dog dog = new Dog("huahua",23);
        return dog;//在這里發生向上轉型
    }

3.2 動態系結

Java中有兩種多型運行時多型(動態系結)編譯時多型(靜態系結)

編譯時多型就是多載來實作的,根據你給的引數以及個數的不同,來推匯出你呼叫那個函式!

運行時多型是怎樣的呢?往下看…

首先一個問題:
在這里插入圖片描述
因為此時發生了動態系結!

發生動態系結兩個條件:

  • 父類參考 參考 子類物件
  • 通過這個父類參考,呼叫父類和子類同名的覆寫方法

大家注意了:動態系結多型的基礎 !!!

這個時候是不是還是有點不明白這個動態是啥意思,接著往下看…

那就來看看下面的位元組碼檔案:

在此之前,我們來看看Java中如何打開反匯編代碼?
在這里插入圖片描述
我們來看一下這個反匯編代碼(main 方法):
在這里插入圖片描述
我們看到反匯編代碼中呼叫的是 Animal.eat; (父類的 eat 方法),但是運行的時候呼叫的為啥是 dog.eat(); (參考的子類物件的 eat 方法)?
在這里插入圖片描述
在這里,在編譯的時候不能夠確定此時呼叫誰的方法;在運行的時候才知道呼叫誰的方法

這個叫運行時系結,也叫動態系結!

知識點補充1:在構造方法中呼叫重寫的方法

直接看代碼:

//Animal類
class Animal {
    public String name = "動物";
    public int age;

    public Animal(String name,int age) {
        eat();//在父類中呼叫父類和子類重寫的eat方法,也會發生所謂的動態系結!
        this.name = name;
        this.age = age;
    }

    public void eat() {
        System.out.println(name+" eat()");
    }
}
//Dog類 繼承于 Animal類
class Dog extends Animal {
    public Dog(String name, int age) {
        super(name, age);//顯示呼叫構造方法
    }

    @Override //這個是注解
    public void eat() {
        System.out.println(name+"crazy eat()");
    }
}
//主類的主方法
public static void main(String[] args) {
        Dog dog = new Dog("wawa",23);//這里創建物件,直接運行,看下面的結果
    }

運行結果如下:
在這里插入圖片描述
說明:在父類中呼叫父類和子類重寫的eat方法也會發生所謂的動態系結

知識點補充2:靜態系結

class Dog {
    public void func(int a) {
        System.out.println("int");
    }

    public void func(int a,int b) {
        System.out.println("int,int");
    }

    public void func(int a,int b,int c) {
        System.out.println("int,int,int");
    }
}
//主類的主方法:
public static void main(String[] args) {
        Dog dog = new Dog("haha",19);
        dog.func(10);
    }

在這里我們打開 PowerShell 視窗查看反匯編代碼:
在這里插入圖片描述
此時這里發生的就是:靜態系結

就是根據你給的引數的型別個數推匯出你呼叫的那個函式

3.3 方法重寫

而這個所謂的父類和子類同名的覆寫方法就是覆寫/重寫/覆寫(Override)!

此時的重寫滿足下面的條件

  1. 方法名相同
  2. 引數串列相同個數型別
  3. 回傳值相同
  4. 必須是父子類的情況下

而且注意:

  • 靜態的方法不能重寫
  • 子類的訪問修飾限定符要大于等于父類的訪問修飾限定
  • private 修飾的方法不能重寫
  • final 修飾的方法不能重寫

但是有一個 special time(很少有書上寫,考試也很少出現):

重寫的時候,回傳值可以不一樣

但是要滿足下面的情況:
在這里插入圖片描述
如果你遇見選擇題的時候,選擇最正確的一個就可以辣!

這里有一點要講一下

上面的 Animal類 回傳的是 AnimalDog類 回傳的是 Dog

它們的回傳值構成了一種型別,叫協變型別

如果你的回傳值發生了協變型別,我們也說發生了重寫

3.4 重寫和多載的區別(重新整理)

重寫(Override): 子類繼承了父類原有的方法,但有時子類并不想原封不動的繼承父類中的某個方法,所以在方法名引數串列回傳型別(除子類中方法的回傳值是父類中方法回傳值的子類時)都相同的情況下, 對方法體進行修改或覆寫即外殼不變,核心重寫!

  • 發生方法重寫的兩個方法回傳值(除了上面寫到的special time和被重寫方法回傳值型別的子類)方法名引數串列必須完全一致(子類重寫父類的方法)
  • 子類方法的訪問級別不能低于父類相應方法的訪問級別
  • 覆寫的方法所拋出的例外和被覆寫方法的所拋出的例外一致或者是其子類(子類例外不能大于父類例外)
  • privatefinalstatic 修飾的方法不能重寫

多載(Overload): 在一個類中,同名的方法如果有不同的引數串列引數型別不同引數個數不同甚至是引數順序不同)則視為多載,同時,多載對回傳型別沒有要求,可以相同也可以不同,所以不能通過回傳型別是否相同來判斷多載

  • 方法名相同,引數串列不同(引數順序、個數、型別)
  • 方法回傳值、訪問修飾符任意

注意點與區別總結:

  • 重寫實作的是運行時的多型,而多載實作的是編譯時的多型
  • 重寫的方法引數串列必須相同(一般情況下);而多載的方法引數串列必須不同
  • 重寫的方法的回傳值型別只能是父型別別或者父型別別的子類,而多載的方法對回傳值型別沒有要求

在這里插入圖片描述

3.5 向下轉型

直接看下面代碼(知識點全部寫進了注釋里):

public static void main(String[] args) {
        Animal animal3 = new Bird("lala",12,"flyyyyy");
        Bird bird = (Bird)animal3;//強行轉換
        bird.fly();//這里可以呼叫fly方法

        //在這里不建議這樣寫,有的時候是錯的(不是非常的安全)!
        //因為不是所有的動物都是鳥,邏輯上就是顛覆認知的!

        //你可以向下轉型;
        //前提是:這個參考(animal3) 參考了 你將要向下轉型的這個物件(bird)!
    }

那為什么說不是非常安全的呢?

//代碼這樣子寫是錯的!
public static void main(String[] args) {
        Animal animal4 = new Dog("aa",23);
        Bird bird = (Bird)animal4;//這里就會報型別轉換例外 
        //因為:不是所有的動物都是鳥!
        bird.fly();
    }

所以,為了讓向下轉型更安全,我們可以先判定一下看看 animal 本質上是不是一個 Bird 實體,再來轉換:

public static void main(String[] args) {
        Animal animal4 = new Dog("aa",23);
        
        if (animal4 instanceof Bird) { //這里if陳述句沒進來
            Bird bird = (Bird)animal4;
            bird.fly();
        }
        //運行之后就是什么都沒有
    }

instanceof 可以判定一個參考是否是某個類的實體

如果是,則回傳 true!

這時再進行向下轉型就比較安全了,

3.6 理解多型

我們先來寫一段代碼:

class Shape {
    public void draw() {
        System.out.println("列印圖形中...");
    }
}
class Rect extends Shape {
    @Override
    public void draw() {
        System.out.println("?");
    }
}
class Flower extends Shape {
    @Override
    public void draw() {
        System.out.println("?");
    }
}
public class TestDemo1 {

    public static void drawMap(Shape shape) {
        shape.draw();//動態系結(運行時),這里呼叫了重寫的draw方法
    }

    public static void main(String[] args) {
        //它的類以及下面物件的類都是Shape類的子類,目的就是為了發生向上轉型!
        Rect rect = new Rect();
        drawMap(rect);
        Flower flower = new Flower();
        drawMap(flower);
    }
}

簡單的定義一下:

通過一個參考呼叫一個 draw方法(父類和子類覆寫的方法),會有不同的表現形式(取決于參考誰的物件),這就是多型

多型的大前提就是一定要向上轉型,且呼叫一個父類方法(子類覆寫,呼叫父類)!

使用多型的好處是什么?

1:類呼叫者對類的使用成本進一步降低

  • 封裝是讓類的呼叫者不需要知道類的實作細節
  • 多型能讓類的呼叫者連這個類的型別是什么都不必知道, 只需要知道這個物件具有某個方法即可

因此,多型可以理解成是封裝的更進一步,讓類呼叫者對類的使用成本進一步降低!

這也貼合了《代碼大全》中關于 “管理代碼復雜程度” 的初衷!

2:能夠降低代碼的 “圈復雜度”, 避免使用大量的 if - else

看代碼:

//不基于多型
public static void main3(String[] args) {
        Rect rect = new Rect();
        Flower flower = new Flower();
        Triangle triangle = new Triangle();

        String[] shapes = {"triangle", "rect", "triangle", "rect", "flower"};
        for (String s : shapes) {
            if(s.equals("triangle")) {
                triangle.draw();
            }else if(s.equals("rect")) {
                rect.draw();
            }else {
                flower.draw();
            }
        }
    }
//基于多型:
//明顯感覺這樣子的代碼更更高級,量更少!
public static void main4(String[] args) {
        Rect rect = new Rect();
        Flower flower = new Flower();
        Triangle triangle = new Triangle();

        Shape[] shapes = {triangle,rect,triangle,rect,flower,};
        for (Shape shape : shapes) {
            shape.draw();
        }
    }

什么叫 “圈復雜度” ?

圈復雜度是一種描述一段代碼復雜程度的方式,一段代碼如果平鋪直敘,那么就比較簡單容易理解,而如果有很
多的條件分支或者回圈陳述句,就認為理解起來更復雜,

因此我們可以簡單粗暴的計算一段代碼中條件陳述句和回圈陳述句出現的個數,這個個數就稱為 “圈復雜度”,如果一
個方法的圈復雜度太高,就需要考慮重構,

不同公司對于代碼的圈復雜度的規范不一樣,一般不會超過 10 ,

3:可擴展能力更強

如果要新增一種新的形狀,使用多型的方式代碼改動成本也比較低!

class Triangle extends Shape{
    @Override
    public void draw() {
        System.out.println("△");
    }
}

對于類的呼叫者來說,只要創建一個新類的實體就可以了,改動成本很低,

而對于不用多型的情況,就要把 drawShapes 中的 if - else 進行一定的修改,改動成本更高,

4. 抽象類

4.1 了解抽象類

文章寫到這里,其實是有一點瑕疵的,不知道大家有沒有發現!狗頭保命!!!

我們回到列印圖形的代碼(父類):

class Shape {
    public void draw() {
        System.out.println("列印圖形中...");
    }
}

我們來思考一下,

其實System.out.println("列印圖形中...");這一段代碼是沒有意義的,因為后面就是類的繼承和方法的重寫(沒有實際作業)!

那接下來的代碼是不是可以這樣寫呢?

//其實這樣寫是錯誤的
class Shape {
    public void draw();
}

然后就有了下面的寫法:

//這里加上 abstract 關鍵字表示這是一個抽象類
abstract class Shape {
    //而這里表示的是一個抽象方法
    abstract public void draw();//抽象方法沒有方法體(沒有 { } , 不能執行具體代碼)!
    
    //同時也注意:包含抽象方法的類叫抽象類
}

4.2 語法規則

  1. 抽象類不能直接實體化(會直接報錯)
    在這里插入圖片描述
  2. 抽象類中可以有普通的方法和成員
  3. 普通類繼承了抽象類,這個普通類中必須重寫抽象類的所有抽象方法(可以被重寫和呼叫)
  4. 抽象方法不能是 private 修飾的

不僅如此,還有一些特殊的規定!

  1. 一個抽象類B繼承了抽象類A,那么這個抽象類A中可以不實作抽象類A的抽象方法!
  2. 在上條繼承關系的基礎上,普通類C繼承了抽象類B,那么A和B中的抽象方法必須被重寫!
  3. 抽象類和抽象方法是不能被 final 修飾!

看代碼是這樣子的:

abstract class A {
    abstract public void draw();//抽象方法
}
abstract class B extends A{ //這里繼承于Shape
    public abstract void funcA();//這里也是抽象方法
    //注意:一個抽象類B繼承了抽象類A,那么這個抽象類A中可以不實作抽象類A的抽象方法!
}
class C extends B {
//在上面繼承關系的基礎上,普通類C繼承了抽象類B,那么A和B中的抽象方法必須被重寫!
    @Override
    public void funcA() {
    }

    @Override
    public void draw() {
    }
}

4.3 抽象類的作用

抽象類存在的最大意義就是為了被繼承!

有些人可能會說普通的類也可以被繼承呀,普通的方法也可以被重寫呀,為啥非得用抽象類和抽象方法呢?

使用抽象類的場景就如上面的代碼,實際作業不應該由父類完成,而應由子類完成

那么此時如果不小心誤用成父類了,使用普通類編譯器是不會報錯的!

這里其實有一個提示報錯的功能,父類是抽象類就會在實體化的時候提示錯誤,讓我們盡早發現問題!

5. 介面

這一節的知識點非常繁瑣(語法比較多),任何知識點都在代碼注釋里(方便直接理解)!

知識點都在代碼注釋里!

知識點都在代碼注釋里!

知識點都在代碼注釋里!

//這個是抽象類借此引出下面的介面!
abstract class Shape {
    abstract public void draw();
}

介面是抽象類的更進一步!

抽象類中還可以包含非抽象方法和欄位

而介面中包含的方法都是抽象方法,欄位只能包含靜態常量

5.1 了解介面及簡單語法規則

//使用 interface 定義一個介面
interface IShape {
    //abstract public void draw();//這里不加abstract public也是可以的!
    //而且注意:介面里面的所有的方法都是 pubilc 的!
    void draw();//抽象方法

    //介面中的普通方法不能有具體的實作!
    //public void func() { //這個方法是錯誤的!
    //} //error

    //如果要實作,就要使用default關鍵字修飾這個方法!
    default public void func() {
        System.out.println("介面中的普通方法...");
    }

    //介面中可以有靜態方法!
    public static void funcStatic() {
        System.out.println("介面中的靜態方法...");
    }
}

介面小總結:

  • 介面中的方法一定是抽象方法,因此可以省略 abstract
  • 介面中的方法一定是 public,因此可以省略 public
//類和介面之間的關系是通過 implements關鍵字 實作的!
class Rect implements IShape {
    //當一個類實作了一個介面,就必須重寫介面中的抽象方法! 
    @Override
    public void draw() {
        System.out.println("?");
    }
    
    @Override
    public void func() {
        System.out.println("重寫介面當中的默認方法");
    }
}

class Flower implements IShape {
    @Override
    public void draw() {
        System.out.println("?");
    }
}

class Triangle implements IShape {
    @Override
    public void draw() {
        System.out.println("△");
    }
}

class Cycle implements IShape {
    @Override
    public void draw() {
        System.out.println("●");
    }
}
//測驗主類:
public class TestDemo3 {

    public static void drawMap(IShape iShape) {
        iShape.draw();//動態系結(運行時),這里呼叫了重寫的draw方法
    }

    public static void main(String[] args) {

        //IShape iShape = new IShape();//錯誤的,介面不能實體化!
        IShape iShape = new Rect();//但是可以發生向上轉型!
        iShape.draw();

        //它的類以及下面物件的類都是Shape類的子類,目的就是為了發生向上轉型!
        Rect rect = new Rect();
        Flower flower = new Flower();
        drawMap(rect);
        drawMap(flower);
    }
}

小總結:

  • 介面不能單獨被實體化
  • 在呼叫的時候同樣可以創建一個介面的參考,對應到一個子類的實體
  • Rect 使用 implements 繼承介面,此時表達的含義不再是 “擴展”,而是 “實作

擴展(extends) 和 實作(implements)區分:

  1. 擴展指的是當前已經有一定的功能了,進一步擴充功能
  2. 實作指的是當前啥都沒有,需要從頭構造出來

5.2 實作多個介面及其他語法規則

同樣的,直接上代碼:

//定義介面 IA
interface IA {
    //介面中的成員變數,默認是 public static final 的!
    //public static final int a = 10;//可以寫成下面的樣子!
    int A = 10;//相當于是常量

    void funcA();//方法就這樣寫,默認是 pubilc abstract 的!
}
//定義介面 IB
interface IB { 
    void funcB();
}
//定義抽象類 B
abstract class B {
    //抽象類B
}

有的時候我們需要讓一個類同時繼承自多個父類

這件事情在有些編程語言通過 多繼承 的方式來實作的,

然而 Java 中只支持單繼承,一個類只能 extends 一個父類!

但是可以同時實作多個介面,也能達到多繼承類似的效果!

//下面的類A繼承了抽象類B(普通類也可以),但是只能單繼承!
//同時,也可以實作多介面,介面之間用逗號隔開!
class A extends B implements IA,IB {
    @Override
    public void funcA() { //當一個類實作一個介面并在重寫方法的時候,方法必須是 pubilc 的!
                          //如果不是 pubilc 權限更加嚴格了,所以無法覆寫
        System.out.println("Override funcA");
        System.out.println(A);//當然也可以訪問介面中的東西!
    }

    @Override
    public void funcB() {
        System.out.println("Override funcB");
    }
}

這樣設計有什么好處呢?

時刻牢記多型的好處,讓程式猿忘記型別

有了介面之后,類的使用者就不必關注具體型別,

而只關注某個類是否具備某種能力

補充提示:

  1. 我們創建介面的時候,介面的命名一般以大寫字母 I 開頭
  2. 介面的命名一般使用 “形容詞” 詞性的單詞(介面表達的含義是 具有 xxx 特性
  3. 阿里編碼規范中約定,介面中的方法和屬性不要加任何修飾符號保持代碼的簡潔性

5.3 介面之間的繼承

剛剛說了,類和介面之間的關系是 implements 操作的,

我想提出的問題是:

那么介面介面之間會存在什么樣的關系呢?

interface IA1 {
    void funcA();
}

//介面和介面之間可以使用extends來操作他們的關系,此時,這里面意為:拓展,
interface IB1 extends IA1 {
    void funcB();
}

class C implements IB1 {
    @Override
    public void funcB() {
        System.out.println("光重寫B還不夠!");
    }

    @Override
    public void funcA() {
        System.out.println("還要重寫A!");
    }
}

一個介面IB1通過extends拓展另一個介面IA1的功能

此時當一個類C通過implements實作這個介面IB1的時候,

此時重寫的方法不僅僅是IB1的抽象方法,還有他從IA1介面拓展來的功能(方法)

5.4 介面使用實體

在這一個小結給大家介紹三個常用的介面

Comparable介面

直接上代碼:

class Student {
    public int age;
    public String name;
    public double score;

    public Student(int age, String name, double score) {
        this.age = age;
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
}

public class TestDemo7 {

    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student(12,"huahua",56);
        students[1] = new Student(23,"wewe",34);
        students[2] = new Student(32,"rous",78);

        System.out.println(Arrays.toString(students));
        Arrays.sort(students);//問題就出在sort這個方法里
        System.out.println(Arrays.toString(students));
    }

}

按照上面運行會報錯的,因為沒有一個可以排序的依靠(沒有一個東西作比較)!

大家感興趣可以多看看底層的代碼!
在這里插入圖片描述
把代碼修改一下后是這樣子的:

//這里直接實作一個Comparable介面
class Student implements Comparable<Student> {
    public int age;
    public String name;
    public double score;

    //這里要重寫下面的方法,就是依靠什么條件來排序的,下面是依靠年齡舉例!
    @Override
    public int compareTo(Student o) { //誰呼叫這個方法 誰就是this
        /*if(this.age > o.age) {
            return 1;
        }else if(this.age == o.age) {
            return 0;
        }else {
            return -1;
        }*/

        //更簡單的實作(從小到大)
        return this.age - o.age;
    }

    public Student(int age, String name, double score) {
        this.age = age;
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
}

public class TestDemo7 {

    //了解compareTo是如何用的!
   /* public static void main(String[] args) {
        Student student1 = new Student(98,"huahua",56);
        Student student2 = new Student(45,"wewe",34);

        System.out.println(student1.compareTo(student2));//這里是一個大于零的數字
    }*/

    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student(12,"huahua",56);
        students[1] = new Student(45,"wewe",34);
        students[2] = new Student(32,"rous",78);

        System.out.println(Arrays.toString(students));
        Arrays.sort(students);//默認是從小到大排序
        System.out.println(Arrays.toString(students));
    }

總結一下就是:如果要進行自定義型別大小的比較,一定要實作可以比較的介面!

但是上面的Comparable介面有一個缺點,如果要換成分數比較代碼改動就比較大

缺點:對類的侵入性非常強,一旦寫好了,不敢輕易改動!

Comparator介面

所以,有一種更好的方式!

就是所說的比較器,直接貼代碼:

class Student {
    public int age;
    public String name;
    public double score;

    public Student(int age, String name, double score) {
        this.age = age;
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
}

//這里有一個快捷鍵 alt+7 就是看里面有啥方法!

class AgeComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.age - o2.age;
    }
}

class Score implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return (int)(o1.score - o2.score);//強制轉化成int型別
    }
}

//主要使用的介面就是這個!
//同樣是今天講解的第二個常用介面!
class NameComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.name.compareTo(o2.name);
    }
}

public class TestDemo7 {

    //了解compareTo是如何用的!
   /* public static void main(String[] args) {
        Student student1 = new Student(98,"huahua",56);
        Student student2 = new Student(45,"wewe",34);

        //了解compareTo是如何用的!
//        System.out.println(student1.compareTo(student2));//這里是一個大于零的數字

        //了解compare是如何用的!
        AgeComparator ageComparator = new AgeComparator();
        System.out.println(ageComparator.compare(student1,student2));//這里是一個大于零的數字
    }*/

    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student(12,"huahua",56);
        students[1] = new Student(45,"wewe",34);
        students[2] = new Student(32,"rous",78);

        System.out.println(Arrays.toString(students));//排序前列印
        AgeComparator ageComparator = new AgeComparator();
        Score score = new Score();
        NameComparator nameComparator = new NameComparator();
        //這里可以使用不同的比較器!
        Arrays.sort(students,nameComparator);//這里ageComparator(傳的就是一個比較器),建議看原始碼!
        System.out.println(Arrays.toString(students));//排序后列印
    }
}

所以,我們就可以推匯出比較器的好處就是:靈活

對類的侵入性非常弱!

那以后是用Comparable介面還是Comparator介面取決于你的業務,一般推薦比較器!

Cloneable介面及深拷貝和淺拷貝

/**
 * 現在講第三個介面!
 *
 * 我們繼續來探討一下 創建物件的方式:
 * 1:new關鍵字
 * 2:實作Cloneable介面
 */

//要想一個類被克隆就要實作Cloneable介面
class Person implements Cloneable{
    public int age;

    public void eat() {
        System.out.println("Eatting!");
    }

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                '}';
    }

    /**
     * clone方法比較特殊,底層是用C/C++實作的,如果要使用它就必須override
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();//這里沒有具體的重寫實作,其實就是呼叫的C/C++代碼!
    }
}


public class Main {

    public static void main(String[] args) throws CloneNotSupportedException {
	Person person = new Person();
    person.age = 99;//這里賦值
    Person person1 = (Person) person.clone();//在記憶體上,拷貝的age也是99!
        //如果是修改拷貝(person1)的值,也不會影響person的值!
    }

    //決定是深拷貝還是淺拷貝,不是方法所決定的,而是代碼的實作!

    //所以說Clone不能說是深拷貝,
    //但是,我們要想辦法讓它變成深拷貝!
}

它的記憶體圖是這樣的:
在這里插入圖片描述

在說如何深拷貝之前,先了解一下Cloneable介面

我們點開Cloneable介面看看代碼
在這里插入圖片描述
在這里就會牽扯一道面試題:你知道Cloneable介面嗎?為啥這個介面是一個空介面?有啥用?

很簡單,因為這個介面是空的,所以是空介面

但是它是一個標志介面代表當前類是可以被克隆的!

然后說一下這個介面咋用:

第一次用的時候,它就會報錯,需要拋例外解決!

按住ALT+ENTER,點擊選項就可以了!
在這里插入圖片描述

接著往下看:

/**
 * 現在來說如何深拷貝!
 * 下面將上面的代碼,進行升華!
 * 在此之前了解什么是淺拷貝!
 */
class Money implements Cloneable{
    public double m = 12.5;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

class Person implements Cloneable{
        public int age;
        public Money money = new Money();

        public void eat() {
            System.out.println("Eatting!");
        }

        @Override
        public String toString() {
            return "Person{" +
                    "age=" + age +
                    '}';
        }

        @Override
        protected Object clone() throws CloneNotSupportedException {
            //return super.clone();//這里沒有具體的重寫實作,其實就是呼叫的C/C++代碼!
            Person tmp = (Person)super.clone();
            tmp.money = (Money) this.money.clone();
            return tmp;
            //上面的操作就是一個 深拷貝 的作用!
        }
    }

public class Main {

    public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person();
        Person person2 = (Person)person.clone();
        System.out.println(person.money.m);
        System.out.println(person2.money.m);
        //列印出來后都是一樣的值,為什么呢?看記憶體圖!
        System.out.println("=====================");
        person2.money.m = 99.99;
        System.out.println(person.money.m);
        System.out.println(person2.money.m);
        //列印出來后都是一樣的值,為什么呢?看記憶體圖!

        //所以這里是淺拷貝!
        //如何深拷貝呢?

        //決定是深拷貝還是淺拷貝,不是方法所決定的,而是代碼的實作!
    }
}

它的記憶體圖是這樣的:
在這里插入圖片描述

如何深拷貝呢?

//就是重寫這個類!
 @Override
        protected Object clone() throws CloneNotSupportedException {
//            return super.clone();//這里沒有具體的重寫實作,其實就是呼叫的C/C++代碼!
            Person tmp = (Person)super.clone();
            tmp.money = (Money) this.money.clone();
            return tmp;
            //上面的操作就是一個 深拷貝 的作用!
        }
    }

它的記憶體圖是這樣的:
在這里插入圖片描述

很重要的一句話:決定是深拷貝還是淺拷貝,不是方法所決定的,而是代碼的實作!

5.5 抽象類和介面的區別

抽象類: 一個類被 abstract 修飾,就直接叫抽象類(定義不重要!)

  • 抽象類不能直接實體化(會直接報錯)

  • 抽象類中可以有普通的方法和成員

  • 普通類繼承了抽象類,這個普通類中必須重寫抽象類的所有抽象方法(可以被重寫和呼叫)

  • 抽象方法不能是 private 修飾的

  • 一個抽象類B繼承了抽象類A,那么這個抽象類A中可以不實作抽象類A的抽象方法!

  • 在上條繼承關系的基礎上,普通類C繼承了抽象類B,那么A和B中的抽象方法必須被重寫!

  • 抽象類和抽象方法是不能被 final 修飾!

介面: 在一個類中,同名的方法如果有不同的引數串列引數型別不同引數個數不同甚至是引數順序不同)則視為多載,同時,多載對回傳型別沒有要求,可以相同也可以不同,所以不能通過回傳型別是否相同來判斷多載

  • 介面不能單獨被實體化
  • 介面中包含的方法都是抽象方法,欄位只能包含靜態常量
  • 介面中的普通方法不能有具體的實作,如果要實作,就要使用 default 關鍵字修飾這個方法!
  • 介面中可以有靜態方法,這個靜態方法中可以有方法體
  • 介面中的方法一定是抽象方法,因此可以省略 abstract
  • 介面中的方法一定是 public,因此可以省略 public

注意點與區別總結:

  • 都不能被單獨實體化
  • 抽象類使用 extends 關鍵字來繼承抽象類;子類使用關鍵字 implements 來實作介面
  • 抽象方法可以有 publicprotecteddefault 這些修飾符;介面方法默認修飾符是 public,不可以使用其它修飾符,
  • 抽象類只能被單繼承;介面可以多實作
  • 抽象類中可以有普通的方法和成員介面中包含的方法都是抽象方法,欄位只能包含靜態常量

在這里插入圖片描述

全文結束

這一篇文章到這里就結束了,期間訪問了大量文獻,包括各種課件、博客、檔案等等!

對了,前面說的一個關于面向物件的訓練,鏈接在左邊!

寫作實屬不易,你們的支持就是我最大的動力!跪求三連!!!

累!

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

標籤:java

上一篇:一文學懂Java泛型,詳細而全面,值得收藏~

下一篇:八大排序——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