十、深度優先 && 廣度優先

原文地址

一、什麼是“搜索”算法?

  • 算法是作用於具體數據結構之上的,深度優先搜索算法和廣度優先搜索算法都是基於“圖”這種數據結構的。
  • 因為圖這種數據結構的表達能力很強,大部分涉及搜索的場景都可以抽象成“圖”。
  • 圖上的搜索算法,最直接的理解就是,在圖中找出從一個頂點出發,到另一個頂點的路徑。
  • 具體方法有很多,兩種最簡單、最“暴力”的方法為深度優先、廣度優先搜索,還有A、 IDA等啟髮式搜索算法。
  • 圖有兩種主要存儲方法,鄰接表和鄰接矩陣。
  • 以無向圖,採用鄰接表存儲為例:
public class Graph {
    // 頂點的個數
    private int v;
    // 每個頂點後面有個鏈表
    private LinkedList<Integer>[] adj;

    public Graph(int v) {
        this.v = v;
        adj = new LinkedList[v];
        for (int i = 0; i < v; i++) {
            adj[i] = new LinkedList<>();
        }
    }

    /**
     * 添加邊
     * @param s 頂點
     * @param t 頂點
     */
    public void addEdge(int s,int t){
        // 無向圖一條邊存兩次(聯想微信好友)
        adj[s].add(t);
        adj[t].add(s);
    }
}

二、廣度優先搜索(BFS)

  • 廣度優先搜索(Breadth-First-Search),簡稱為 BFS。
  • 它是一種“地毯式”層層推進的搜索策略,即先查找離起始頂點最近的,然後是次近的,依次往外搜索

2.1、實現過程

/**
 * 圖的廣度優先搜索,搜索一條從 s 到 t 的路徑。
 * 這樣求得的路徑就是從 s 到 t 的最短路徑。
 *
 * @param s 起始頂點
 * @param t 終止頂點
 */
public void bfs(int s, int t) {
    if (s == t) {
        return;
    }
    // visited 記錄已經被訪問的頂點,避免頂點被重複訪問。如果頂點 q 被訪問,那相應的visited[q]會被設置為true。
    boolean[] visited = new boolean[v];
    visited[s] = true;
    // queue 是一個隊列,用來存儲已經被訪問、但相連的頂點還沒有被訪問的頂點。因為廣度優先搜索是逐層訪問的,只有把第k層的頂點都訪問完成之後,才能訪問第k+1層的頂點。
    // 當訪問到第k層的頂點的時候,需要把第k層的頂點記錄下來,稍後才能通過第k層的頂點來找第k+1層的頂點。
    // 所以,用這個隊列來實現記錄的功能。
    Queue<Integer> queue = new LinkedList<>();
    queue.add(s);
    // prev 用來記錄搜索路徑。當從頂點s開始,廣度優先搜索到頂點t后,prev數組中存儲的就是搜索的路徑。
    // 不過,這個路徑是反向存儲的。prev[w]存儲的是,頂點w是從哪個前驅頂點遍歷過來的。
    // 比如,通過頂點2的鄰接表訪問到頂點3,那prev[3]就等於2。為了正向打印出路徑,需要遞歸地來打印,就是print()函數的實現方式。
    int[] prev = Arrays.stream(new int[v]).map(f -> -1).toArray();

    while (queue.size() != 0) {
        int w = queue.poll();
        LinkedList<Integer> wLinked = adj[w]; // 表示:鄰接表存儲時頂點為w,所對應的鏈表
        for (int i = 0; i < wLinked.size(); ++i) {
            int q = wLinked.get(i);
            // 判斷頂點 q 是否被訪問
            if (!visited[q]) {
                // 未被訪問
                prev[q] = w;
                if (q == t) {
                    print(prev, s, t);
                    return;
                }
                visited[q] = true;
                queue.add(q);
            }
        }
    }
}

// 遞歸打印s->t的路徑
private void print(int[] prev, int s, int t) {
    if (prev[t] != -1 && t != s) {
        print(prev, s, prev[t]);
    }
    System.out.print(t + " ");
}

原理如下:

2.2、複雜度分析

  • 最壞情況下,終止頂點 t 離起始頂點 s 很遠,需要遍歷完整個圖才能找到。
  • 這個時候,每個頂點都要進出一遍隊列,每個邊也都會被訪問一次,所以,廣度優先搜索的時間複雜度是 O(V+E)
  • 其中,V 表示頂點的個數,E 表示邊的個數。
  • 對於一個連通圖來說,也就是說一個圖中的所有頂點都是連通的,E肯定要大於等於 V-1,所以,廣度優先搜索的時間複雜度也可以簡寫為 O(E)。
  • 廣度優先搜索的空間消耗主要在幾個輔助變量 visited 數組、queue 隊列、prev 數組上。
  • 這三個存儲空間的大小都不會超過頂點的個數,所以空間複雜度是 O(V)

三、深度優先搜索(DFS)

  • 深度優先搜索(Depth-First-Search),簡稱DFS。
  • 最直觀的例子就是“走迷宮,假設站在迷宮的某個岔路口,然後想找到出口。
  • 隨意選擇一個岔路口來走,走着走着發現走不通的時候,就回退到上一個岔路口,重新選擇一條路繼續走,直到最終找到出口。這種走法就是一種深度優先搜索策略。
  • 如下圖所示,在圖中應用深度優先搜索,來找某個頂點到另一個頂點的路徑。
  • 搜索的起始頂點是 s,終止頂點是 t,在圖中尋找一條從頂點 s 到頂點 t 的路徑。
  • 用深度遞歸算法,把整個搜索的路徑標記出來了。實線箭頭表示遍歷,虛線箭頭表示回退。
  • 從圖中可以看出,深度優先搜索找出來的路徑,並不是頂點 s 到頂點 t 的最短路徑。

3.1、實現過程

// 全局變量或者類成員變量,標記是否找到終點 t
boolean found = false;

/**
 * 深度優先搜索
 *
 * @param s 起始頂點
 * @param t 終止頂點
 */
public void dfs(int s, int t) {
    found = false;
    // 標記頂點是否被訪問
    boolean[] visited = new boolean[v];
    // prev 用來記錄搜索路徑,prev[w] = a 表示 w 頂點的上一級節點為 a
    int[] prev = Arrays.stream(new int[v])
            .map(f -> -1).toArray();

    recurDfs(s, t, visited, prev);
    print(prev, s, t);
}

private void recurDfs(int w, int t, boolean[] visited, int[] prev) {
    if (found == true) {
        return;
    }
    visited[w] = true;
    if (w == t) {
        found = true;
        return;
    }
    LinkedList<Integer> wLinked = adj[w];
    for (int i = 0; i < wLinked.size(); ++i) {
        int q = wLinked.get(i);
        if (!visited[q]) {
            prev[q] = w;
            recurDfs(q, t, visited, prev);
        }
    }
}

3.2、複雜度分析

  • 深度搜索中每條邊最多會被訪問兩次,一次是遍歷,一次是回退。
  • 所以,深度優先搜索算法的時間複雜度是 O(E), E 表示邊的個數。
  • 深度優先搜索算法的消耗內存主要是 visited、 prev 數組和遞歸調用棧。
  • visited、 prev 數組的大小跟頂點的個數V成正比,遞歸調用棧的最大深度不會超過頂點的個數,所以總的空間複雜度就是 O(V)

四,兩者對比

  • 廣度優先搜索和深度優先搜索是圖上的兩種最常用、最基本的搜索算法,比起其他高級的搜索算法,比如A、 IDA等,要簡單粗暴,沒有什麼優化,所以,也被
    叫作暴力搜索算法。
  • 所以,這兩種搜索算法僅適用於狀態空間不大,也就是說圖不大的搜索。
  • 廣度優先搜索,通俗的理解就是,地毯式層層推進,從起始頂點開始,依次往外遍歷。
  • 廣度優先搜索需要藉助隊列來實現,遍歷得到的路徑就是,起始頂點到終止頂點的最短路徑。
  • 深度優先搜索用的是回溯思想,非常適合用遞歸實現。換種說法,深度優先搜索是藉助棧來實現的。
  • 在執行效率方面,深度優先和廣度優先搜索的時間複雜度都是 O(E),空間複雜度是 O(V)。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

springboot的jar為何能獨立運行

歡迎訪問我的GitHub

https://github.com/zq2599/blog_demos
內容:所有原創文章分類匯總及配套源碼,涉及Java、Docker、Kubernetes、DevOPS等;

能獨立運行的jar文件

在開發springboot應用時,通過java -jar命令啟動應用是常用的方式,今天就來一起了解這個簡單操作背後的技術;

開發demo

開發一個springboot應用作為本次研究的對象,對應的版本信息如下:

  • JDK:1.8.0_211
  • springboot:2.3.1.RELEASE
  • maven:3.6.0

接下來開發springboot應用,這個應用異常簡單:

  1. springboot應用名為springbootstarterdemo,pom.xml文件內容:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.bolingcavalry</groupId>
    <artifactId>springbootstarterdemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springbootstarterdemo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
  1. 只有一個java類,裏面有個http接口:
package com.bolingcavalry.springbootstarterdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

@SpringBootApplication
@RestController
public class SpringbootstarterdemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootstarterdemoApplication.class, args);
    }
    @RequestMapping(value = "/hello")
    public String hello(){
        return "hello " + new Date();
    }
}
  1. 編碼完成,在pom.xml所在目錄執行命令
mvn clean package -U -DskipTests
  1. 構建成功后,在target目錄下得到文件springbootstarterdemo-0.0.1-SNAPSHOT.jar
  2. 就是這個springbootstarterdemo-0.0.1-SNAPSHOT.jar,此時執行java -jar springbootstarterdemo-0.0.1-SNAPSHOT.jar就能啟動應用,如下圖:

接下來就用這個springbootstarterdemo-0.0.1-SNAPSHOT.jar來分析jar文件能夠獨立啟動的原因;

java -jar做了什麼

  • 先要弄清楚java -jar命令做了什麼,在oracle官網找到了該命令的描述:

    If the -jar option is specified, its argument is the name of the JAR file containing class and resource files for the application. The startup class must be indicated by the Main-Class manifest header in its source code.

  • 再次秀出我蹩腳的英文翻譯:

  1. 使用-jar參數時,後面的參數是的jar文件名(本例中是springbootstarterdemo-0.0.1-SNAPSHOT.jar);
  2. 該jar文件中包含的是class和資源文件;
  3. 在manifest文件中有Main-Class的定義;
  4. Main-Class的源碼中指定了整個應用的啟動類;(in its source code)
  • 小結一下:
    java -jar會去找jar中的manifest文件,在那裡面找到真正的啟動類;

探查springbootstarterdemo-0.0.1-SNAPSHOT.jar

  1. springbootstarterdemo-0.0.1-SNAPSHOT.jar是前面的springboot工程的構建結果,是個壓縮包,用常見的壓縮工具就能解壓,我這裏的環境是MacBook Pro,用unzip即可解壓;

  2. 解壓後有很多內容,我們先關注manifest相關的,下圖紅框中就是manifest文件:

  3. 打開上圖紅框中的文件,內容如下:

Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: springbootstarterdemo
Implementation-Version: 0.0.1-SNAPSHOT
Start-Class: com.bolingcavalry.springbootstarterdemo.Springbootstarter
 demoApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.3.1.RELEASE
Created-By: Maven Jar Plugin 3.2.0
Implementation-Vendor: Pivotal Software, Inc.
Main-Class: org.springframework.boot.loader.JarLauncher
  1. 在上述內容可見Main-Class的值org.springframework.boot.loader.JarLauncher,這個和前面的java官方文檔對應上了,正是這個JarLauncher類的代碼中指定了真正的啟動類;

疑惑出現

  1. 在MANIFEST.MF文件中有這麼一行內容:
Start-Class: com.bolingcavalry.springbootstarterdemo.Springbootstarter
 demoApplication
  1. 前面的java官方文檔中,只提到過Main-Class ,並沒有提到Start-Class
  2. Start-Class的值是SpringbootstarterdemoApplication,這是我們的java代碼中的唯一類,也只真正的應用啟動類;
  3. 所以問題就來了:理論上看,執行java -jar命令時JarLauncher類會被執行,但實際上是SpringbootstarterdemoApplication被執行了,這其中發生了什麼呢?

猜測

動手之前先猜一下,個人覺得原因應該如下:

  1. java -jar命令會啟動JarLauncher;
  2. Start-Class是給JarLauncher用的;
  3. JarLauncher根據Start-Class找到了SpringbootstarterdemoApplication,然後執行它;

分析JarLauncher

  1. 先下載SpringBoot源碼,我下載的是2.3.1版本,地址:https://github.com/spring-projects/spring-boot/releases/tag/v2.3.1.RELEASE

  2. JarLauncher所在的工程是spring-boot-loader,先弄明白JarLauncher的繼承關係,如下圖,可見JarLauncher繼承自ExecutableArchiveLauncher,而ExecutableArchiveLauncher的父類Launcher位於最頂層,是個抽象類:

  3. java -jar執行的是JarLauncher的main方法,如下,會實例化一個JarLauncher對象,然後執行其launch方法,並且將所有入參都帶入:

public static void main(String[] args) throws Exception {
	new JarLauncher().launch(args);
}
  1. 上面的launch方法在父類Launcher中:
protected void launch(String[] args) throws Exception {
    // 將jar解壓后運行的方式叫做exploded mode
    // 如果是exploded mode,就不能支持通過URL加載jar
    // 如果不是exploded mode,就可以通過URL加載jar
	if (!isExploded()) {
	    // 如果允許通過URL加載jar,就在此註冊對應的處理類
		JarFile.registerUrlProtocolHandler();
	}
	// 創建classLoader
	ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
	// jarmode是創建docker鏡像時用到的參數,使用該參數是為了生成帶有多個layer信息的鏡像
	// 這裏暫時不關注jarmode
	String jarMode = System.getProperty("jarmode");
	//如果沒有jarmode參數,launchClass的值就來自getMainClass()返回
	String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
	launch(args, launchClass, classLoader);
}
  1. 可見要重點關注的是getMainClass()方法,在看這個方法之前,我們先去關注一個重要的成員變量archive,是JarLauncher的父類ExecutableArchiveLauncher的archive,如下可見,該變量又來自方法createArchive:
public ExecutableArchiveLauncher() {
		try {
			this.archive = createArchive();
			this.classPathIndex = getClassPathIndex(this.archive);
		}
		catch (Exception ex) {
			throw new IllegalStateException(ex);
		}
	}
  1. 方法來自Launcher.createArchive,如下所示,可見成員變量archive實際上是個JarFileArchive對象:
protected final Archive createArchive() throws Exception {
		ProtectionDomain protectionDomain = getClass().getProtectionDomain();
		CodeSource codeSource = protectionDomain.getCodeSource();
		URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;
		String path = (location != null) ? location.getSchemeSpecificPart() : null;
		if (path == null) {
			throw new IllegalStateException("Unable to determine code source archive");
		}
		File root = new File(path);
		if (!root.exists()) {
			throw new IllegalStateException("Unable to determine code source archive from " + root);
		}
		return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
	}
  1. 現在回到getMainClass()方法,可見his.archive.getManifest方法返回的是META-INF/MANIFEST.MF文件的內容,然後getValue(START_CLASS_ATTRIBUTE)方法實際上就是從META-INF/MANIFEST.MF中取得了Start-Class的屬性:
@Override
	protected String getMainClass() throws Exception {
	    // 對應的是JarFileArchive.getManifest方法,
	    // 進去后發現對應的就是JarFile.getManifest方法,
	    // JarFile.getManifest對應的就是META-INF/MANIFEST.MF文件的內容
		Manifest manifest = this.archive.getManifest();
		String mainClass = null;
		if (manifest != null) {
		    // 對應的是META-INF/MANIFEST.MF文件中的Start-Class的屬性
			mainClass = manifest.getMainAttributes().getValue(START_CLASS_ATTRIBUTE);
		}
		if (mainClass == null) {
			throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
		}
		return mainClass;
	}
  1. 從上述分析可知:getMainClass()方法返回的是META-INF/MANIFEST.MF中取得了Start-Class的屬性com.bolingcavalry.springbootstarterdemo.SpringbootstarterdemoApplication,再次回到launch方法中,可見最終運行的代碼是launch(args, launchClass, classLoader),它的launchClass參數就是com.bolingcavalry.springbootstarterdemo.SpringbootstarterdemoApplication:
protected void launch(String[] args) throws Exception {
		if (!isExploded()) {
			JarFile.registerUrlProtocolHandler();
		}
		ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
		String jarMode = System.getProperty("jarmode");
		// 這裏的launchClass等於"com.bolingcavalry.springbootstarterdemo.SpringbootstarterdemoApplication"
		String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
		// 這裏就是啟動SpringbootstarterdemoApplication的地方
		launch(args, launchClass, classLoader);
	}
  1. 展開launch(args, launchClass, classLoader),最終查到了MainMethodRunner類:
public class MainMethodRunner {

	private final String mainClassName;

	private final String[] args;

	/**
	 * Create a new {@link MainMethodRunner} instance.
	 * @param mainClass the main class
	 * @param args incoming arguments
	 */
	public MainMethodRunner(String mainClass, String[] args) {
	    // mainClassName被賦值為"com.bolingcavalry.springbootstarterdemo.SpringbootstarterdemoApplication"
		this.mainClassName = mainClass;
		this.args = (args != null) ? args.clone() : null;
	}

	public void run() throws Exception {
	    // 得到SpringbootstarterdemoApplication的Class對象
		Class<?> mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader());
		// 得到SpringbootstarterdemoApplication的main方法對象
		Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
		mainMethod.setAccessible(true);
		// 通過反射執行main方法
		mainMethod.invoke(null, new Object[] { this.args });
	}
}

終於,真相大白了;

小結

最後盡可能簡短做個小結,先看jar是如何產生的,如下圖,maven插件生成的jar文件中,有常見的class、jar,也有符合java規範的MANIFEST.MF文件,並且,還在MANIFEST.MF文件中額外生成了名為Start-Class的配置,這裏面是我們編寫的應用啟動類SpringbootstarterdemoApplication

啟動類是JarLauncher,它是如何與MANIFEST.MF文件關聯的呢?從下圖可以看出,最終是通過JarFile類的成員變量manifestSupplier關聯上的:

再來看看關鍵代碼的執行情況,如下圖:

至此,SpringBoot的jar獨立運行的基本原理已經清楚,探究的過程中,除了熟悉關鍵代碼流程,還對jar中的文件有了更多了解,如果您正在學習SpringBoot,希望本文能給您一些參考;

官方文檔

  1. 最後附上SpringBoot官方文檔,可以看到Start-Class描述信息:

  2. 上述文檔明確提到:Start-Class定義的是實際的啟動類,此時的您應該對一切都瞭然於胸,產生本該如此的感慨;

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

中國最大太陽能電動車充電站 於北京CBD首度投用

據報導,位於中國北京CBD地區華茂中心停車場的太陽能電動汽車公共充電站於近日正式建成並投入試運營。這座中國目前最大的電動汽車智慧充電站共有100台充電樁,包括兩座地面太陽能智慧充電站和地下充電站。   該電站太陽能模組轉換效率達22%,裝機容量目前為25kW,投資額約為1300萬元人民幣(下同)。 此充電站收費標準初步定在1.37元人民幣/kWh,其中含有0.57元人民幣的基礎電費。其提供快充和慢充兩種充電服務,慢充最長時間為兩個半小時,快充僅需20分鐘。該充電站不僅可為市場上幾乎所有類型的電動車充電,且車主可通過手機達到預約、支付等充電操作。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

印度南部核電廠曾遭網攻 所幸系統未受影響

摘錄自2019年10月31日中央社報導

曾有印度媒體披露,印度核電公司(The NuclearPower Corporation of India, NCPIL)的古丹庫蘭(Kudankulam)核電廠系統曾於9月遭植入惡意軟體(malware)並受到網路攻擊。印度核電公司31日發表聲明證實,位於印度南部坦米爾那都省(TamilNadu)的古丹庫蘭核電廠曾遭網路攻擊,但聲明也強調,核電廠系統沒有受到影響。

這份聲明也證實,公司的系統被植入惡意軟體,印度電腦緊急應變小組(CERT-In)9月4日發現這種狀況後已通知公司。

聲明說,全案已由印度原子能部(Department ofAtomic Energy)專家進行調查,初步發現受感染的電腦屬於同一用戶,這名用戶的電腦曾與用於管理系統的網絡連結;受感染部份目前與關鍵內部網路隔離,且網路持續被監控中。核電廠系統沒有受到影響。

原能會對此事件也發布新聞稿表示:我國運轉中核能電廠的發電及安全相關電腦數位設備都是獨立的系統,與外界實體隔離,阻絕網路攻擊的機會,並訂有隨身碟資訊設備及門禁管制程序,以避免駭客透過網際網路以外之其他方式入侵。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

民主剛果抗伊波拉鬥士家中遇害 凶手動機不明

摘錄自2019年11月4日中央社報導

法新社報導,剛果民主共和國軍方4日表示說,一名協助傳遞對抗伊波拉(Ebola)疫情資訊的電台主持人,35歲的馬罕巴(Papy Mumbere Mahamba),他的妻子受傷,住家被縱火燒毀他的家。

這起在動盪的伊圖里省(Ituri)魯汶巴鎮(Lwemba)發生的謀殺案,凶手犯案動機不明。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

合掌村失火!幸未殃及主聚落

摘錄自2019年11月5日聯合報報導

日本著名景點、岐阜縣大野群白川村的荻町附近木屋四日發生火災,事發時日本推特上不斷傳出被登錄為世界遺產的合掌村附近有木屋被烈焰吞噬,冒出濃煙,消防車警笛大作,幸好無人受傷,火勢也沒有延燒到合掌村。

起火地點並非被指定為重要文化財的合掌村聚落,而是距離五百公尺遠的一間木屋。附近店家表示,失火的木屋平日為車庫,在遊客用停車場的旁邊。起火處有白川鄉點燈活動使用的電源設備,目前不知道火災與其有無關連。但4日的點燈活動取消。



照片取自twitter

うぉーい!!
白川郷で火事起きてるぜ!?

— じゅりあーに3世 (@Messiah3rd)

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

三星收購電池部門 傳將研發電動車

蘋果(Apple)研發電動車的傳聞滿天飛,三星(Samsung)不甘示弱跟進,打算在電動車界跟蘋果再次一決雌雄?   韓聯社和 Tomˋs Hardware 報導,南韓電池製造龍頭 Samsung SDI 23 日宣布收購汽車零件商 Magna Steyr 的電池部門,購併金額未對外透露。   Tomˋs Hardware 網站認為,Samsung SDI 是 BMW i3 電動車的電池供應商,購併固然可能只是要強化電池部門實力,但也有可能代表三星打算跨足電動車。三星集團旗下業務橫跨電機、化學、造船、工具機等,如果真的要生產電動車,三星握有的技術資源更勝蘋果。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

台達電電動車驅動馬達 打入福斯供應鏈

電源供應器廠積極跨入車用市場,台達電耕耘 3 年的電動車驅動馬達打入福斯集團供應鏈,今年開始出貨;新巨微動開關則陸續取得德國雙 B、奧迪與福斯,美系福特和日系車廠訂單;康舒電源轉換器也與多家國際電動車大廠談合作。   台達電董事長海英俊表示,目前各國積極發展電動車,基礎設施也開始動起來,因此台達電最近在電動車零組件有不錯的進展,包括出貨給美國知名電動車廠充電樁、轉換器,台達電在歐、美、日、中國等各地的電動車標準都通過認證。   海英俊透露,台達電研發電動車驅動馬達,經過 3 年努力,已獲歐洲車廠認證。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

雷軍:小米3至5年內不做電動車

今日,雷軍出席亞布力中國企業家論壇第十五屆年會,並發表了演講。對於進入電動車領域的傳聞,雷軍稱小米三到五年內不會涉及,主要目標是把現有產品做好。   他明確表示,小米佈局已經完成,包括三類、五種產品,包括手機、平板、電視、電視盒、路由器等,主要是為了智慧家居。他稱,「(不涉及電動車)跟市場好壞沒關係,我們專注的把現在幾個產品做好,就已經很不容易了。不是不看好,是精力有限。」

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

Python 簡明教程 — 20,Python 類中的屬性與方法

微信公眾號:碼農充電站pro
個人主頁:https://codeshellme.github.io

與客戶保持良好的關係可以使生產率加倍。
—— Larry Bernstain

目錄

類中的變量稱為屬性,類中的函數稱為方法

類中的屬性分為:

  • 實例屬性:對象所有,互不干擾
  • 類屬性:類所有,所有對象共享

類中的方法分為:

  • 實例方法:定義中有self 參數
  • 類方法:定義中有cls 參數,使用@classmethod 裝飾器
  • 靜態方法:定義中即沒有self 參數,也沒有cls 參數,使用@staticmethod 裝飾器

1,實例屬性與類屬性

類的對象,就是類的一個實例。類的實例屬性被對象所有,包含在每個對象之中,不同的對象之間,互不干擾。類的類屬性被類所有,被包含在類中,是所有的類對象共享。

一般情況下,實例屬性會在__init__ 方法中聲明並初始化,並且使用self 來綁定。而類屬性是在類作用域中被聲明,並且不使用self 來綁定。

例如下面代碼中,country 為類屬性,__name 為實例屬性:

#! /usr/bin/env python3

class People:

    country = 'china'

    def __init__(self, name):
        self.__name = name

訪問實例屬性時使用對象.屬性名的格式,實例屬性屬於對象各自的,互不影響:

>>> p1 = People('小明')
>>> p2 = People('小美')
>>> 
>>> p1.get_name()
'小明'
>>> p2.get_name()
'小美'

類屬性被所有對象共有,一旦被改變,所有對象獲取到的值都會被改變。訪問類屬性時使用類名.屬性名的格式,也可以使用對象.屬性名的格式來訪問:

>>> People.country              # 用`類名.屬性名`的格式訪問
'CHINA'
>>> p1.country                  # 用`對象.屬性名`的格式訪問
'china'
>>> p2.country
'china'
>>> 
>>> People.country = 'CHINA'    # 類屬性的值被改變
>>> p1.country                  # 每個對象獲取到的值也會被改變
'CHINA'
>>> p2.country
'CHINA'

注意,在使用對象.屬性名的格式訪問對象時,不要以這種格式對類屬性進行賦值,否則結果可能不會像你想象的一樣:

>>> p1 = People('小明')
>>> p2 = People('小美')
>>> People.country
'china'
>>> p1.country
'china'
>>> p2.country
'china'
>>> p1.country = '中國'   # 使用`對象.屬性名`的格式對類對象進行賦值
>>> p1.country           # 只有 p1.country 被改變
'中國'
>>> p2.country           # p2.country 沒有被改變
'china'
>>> People.country       # People.country 也沒有被改變
'china'

從上面代碼中可以看到,在Python 中以對象.屬性名格式對類屬性進行賦值時,只有p1.country 的值被改變了,p2.countryPeople.country 的值都沒有被改變。

實際上,這種情況下,類屬性的值並沒有被改變,而是對象p1 中多了一個country 實例屬性,此後,p1.country 訪問的是p1 的實例屬性,p1.country對屬性country的訪問屏蔽了類中的country屬性,而p2.countryPeople.country 訪問的依然是原來的類屬性

所以,類名.屬性名對象.屬性名的格式都可以訪問類屬性的值,但盡量避免使用對象.屬性名的格式對類屬性的值進行賦值,否則可能會發生混亂。

建議:

不管是訪問還是改變類屬性的值,都只用類名.屬性名的格式

2,實例方法,類方法,靜態方法

Python 類中有三種方法:

  • 實例方法
  • 類方法
  • 靜態方法

實例方法屬於對象,方法中都有一個self 參數(代表對象本身)。實例方法只能由對象調用,不能通過類名訪問。實例方法中可以訪問實例屬性和類屬性。

類方法屬於類,方法中都有一個cls 參數(代表類本身)。類方法即可以通過類名訪問,也可以通過對象訪問,類方法中只能訪問類屬性,不能訪問實例屬性。

注意:

Python 解釋器在構造類與對象時,是先於對象產生的。

因此,類屬性與類方法是先於實例屬性與實例方法 產生的。

所以當類方法產生時,還沒有實例屬性,因此,類方法中不能訪問實例屬性。

靜態方法中,沒有self 參數,也沒有cls 參數。因此,在靜態方法中,即不能訪問類屬性,也不能訪問實例屬性。靜態方法可以使用類名訪問,也可以使用對象訪問。

在Python 中,定義類方法需要用到裝飾器@classmethod,定義靜態方法需要用到裝飾器@staticmethod

實例方法演示

#! /usr/bin/env python3

class People:

    country = 'china'

    def __init__(self, name):
        self.__name = name

    # 實例方法中有self 參數
    def instance_method_test(self):
        # 在實例方法中訪問了實例屬性和類屬性
        print('name:%s country:%s' % (self.__name, People.country))

使用:

>>> p = People('小明')
>>> p.instance_method_test()
name:小明 country:china

在實例方法中訪問了實例屬性__name和類屬性country,均可以被訪問。

類方法演示

#! /usr/bin/env python3

class People:

    country = 'china'

    def __init__(self, name):
        self.__name = name

    # 類方法中都有cls 參數
    @classmethod
    def class_method_test1(cls):
        print('在類方法中訪問類屬性country:%s' % cls.country)

    @classmethod
    def class_method_test2(cls):
        print('在類方法中訪問實例屬性__name:%s' % self.__name)

使用:

>>> p = People('小明')
>>> p.class_method_test1()         # 在類方法中訪問類屬性,可以
在類方法中訪問類屬性country:china
>>>
>>> p.class_method_test2()         # 在類方法中訪問實例屬性,出現異常
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/wp/to_beijing/People.py", line 18, in class_method_test2
    print('在類方法中訪問實例屬性__name:%s' % self.__name)
NameError: name 'self' is not defined

從上面代碼中可以看到,在類方法中可以訪問類屬性,但在類方法中訪問實例屬性,會出現異常。

靜態方法演示

#! /usr/bin/env python3

class People:

    country = 'china'

    def __init__(self, name):
        self.__name = name

    # 靜態方法中即沒有self 參數也不沒有cls 參數
    @staticmethod
    def static_method_test1():
        print('在靜態方法中訪問類屬性country:%s' % cls.country)

    @staticmethod
    def static_method_test2():
        print('在靜態方法中訪問實例屬性__name:%s' % self.__name)

使用:

>>> p = People('小明')
>>> p.static_method_test1()      # 在靜態方法中訪問類屬性,出現異常
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/wp/to_beijing/People.py", line 14, in static_method_test1
    print('在靜態方法中訪問類屬性country:%s' % cls.country)
NameError: name 'cls' is not defined
>>>
>>> p.static_method_test2()     # 在靜態方法中訪問實例屬性,出現異常
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/wp/to_beijing/People.py", line 18, in static_method_test2
    print('在靜態方法中訪問實例屬性__name:%s' % self.__name)
NameError: name 'self' is not defined

從上面代碼中可以看到,在靜態方法中無論訪問實例方法還是類方法,都會出現異常。

3,專有方法

我們之前講到過的魔法方法,即以雙下劃線__開頭且結尾的方法__xxx__,就是專有方法。這些方法都被Python 賦予了特殊的含義,用戶可以根據需要,來實現這些方法。

下面我們介紹一些 Python 中常見的專有方法。

__len__ 方法

實現該方法的類的對象,可以用len() 函數計算其長度。

__str__ 方法

實現該方法的類的對象,可以轉化為字符串。

__call__ 方法

實現該方法的類的對象,可以像函數一樣調用。

__iter__ 方法

實現該方法的類的對象,是可迭代的。

__setitem__ 方法

實現該方法的類的對象,可以用索引的方式進行賦值。

__getitem__ 方法

實現該方法的類的對象,可以用索引的方式進行訪問。

__contains__ 方法

實現該方法的類的對象,可以進行in 運算。

__add__ 方法

實現該方法的類的對象,可以進行加+運算。

__sub__ 方法

實現該方法的類的對象,可以進行減-運算。

__mul__ 方法

實現該方法的類的對象,可以進行乘*運算。

__div__ 方法

實現該方法的類的對象,可以進行除/運算。

__pow__ 方法

實現該方法的類的對象,可以進行乘方運算。

__mod__ 方法

實現該方法的類的對象,可以進行取模運算。

__eq__ 方法

實現該方法的類的對象,可以進行相等==比較。

__ne__ 方法

實現該方法的類的對象,可以進行不等於!=比較。

__gt__ 方法

實現該方法的類的對象,可以進行大於>比較。

__ge__ 方法

實現該方法的類的對象,可以進行大於等於>=比較。

__lt__ 方法

實現該方法的類的對象,可以進行小於<比較。

__le__ 方法

實現該方法的類的對象,可以進行小於等於<=比較。

(完。)

推薦閱讀:

Python 簡明教程 — 15,Python 函數

Python 簡明教程 — 16,Python 高階函數

Python 簡明教程 — 17,Python 模塊與包

Python 簡明教程 — 18,Python 面向對象

Python 簡明教程 — 19,Python 類與對象

歡迎關注作者公眾號,獲取更多技術乾貨。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案