主頁 > 後端開發 > 三十分鐘入門基礎Go(Java小子版)

三十分鐘入門基礎Go(Java小子版)

2023-02-15 06:56:13 後端開發

作者:京東科技 韓國凱

前言

Go語言定義

Go(又稱 Golang)是 Google 的 Robert Griesemer,Rob Pike 及 Ken Thompson 開發的一種靜態、強型別、編譯型語言,Go 語言語法與 C 相近,但功能上有:記憶體安全,GC,結構形態及 CSP-style 并發計算

適用范圍

本篇文章適用于學習過其他面向物件語言(Java、Php),但沒有學過Go語言的初學者,文章主要從Go與Java功能上的對比來闡述Go語言的基礎語法、面向物件編程、并發與錯誤四個方面,

一、基礎語法

Go語言的基礎語法與常規的編程語言基本類似,所不同的有宣告變數的方式,陣列、切片、字典的概念及功能與Java不太相同,不過Java中這些資料結構都可以通過類比功能的方式在Go中使用,

1.1 變數、常量、nil與零值、方法、包、可見性、指標

1.1.1 變數宣告

Go語言中有兩種方式

1.使用var關鍵字宣告,且需要注意的是,與大多數強型別語言不同,Go語言的宣告變數型別位于變數名稱的后面,Go陳述句結束不需要分號,

var num int

var result string = "this is result"

2.使用:=賦值,

num := 3 等同于 var num int = 3

其中變數的型別會根據右側的值進行匹配,例如"3"會匹配為int,"3.0"會匹配為float64,"result"會匹配為string,

1.1.2 常量宣告

使用const來宣告一個常量,一個常量在宣告后不可改變,

const laugh string = "go"

1.1.3 nil與零值

只宣告未賦值的變數,其值為nil,類似于java中的“null”

沒有明確初始值的變數宣告會被賦予它們的 零值

零值是:

  • 數值型別為 0
  • 布爾型別為 false
  • 字串為 ""(空字串),

1.1.4 方法、包

Go中方法的定義

使用func關鍵字來定義一個方法,后面跟方法名,然后是引數,回傳值(如果有的話,沒有回傳值則不寫),

func MethodName(p1 Parm, p2 Parm) int{}

//學習一個語言應該從Hello World開始!
package main

import "fmt"

func main() {
	fmt.Println("Hello World!")// Hello World!
    fmt.Println(add(3, 5)) //8
    var sum = add(3, 5)
}

func add(a int, b int) int{
    return a+b;
}
多個回傳值

Go 函式與其他編程語言一大不同之處在于支持多回傳值,這在處理程式出錯的時候非常有用,例如,如果上述 add 函式只支持非負整數相加,傳入負數則會報錯,

//回傳值只定義了型別 沒有定義回傳引數
func add(a, b int) (int, error) {
    if a < 0 || b < 0 {
        err := errors.New("只支持非負整數相加")
        return 0, err
    }
    a *= 2
    b *= 3
    return a + b, nil
}

//回傳值還定義了引數 這樣可以直接return 并且定義的引數可以直接使用 return時只會回傳這兩個引數
func add1(a, b int) (z int, err error) {
    if a < 0 || b < 0 {
        err := errors.New("只支持非負整數相加")
        return   //實際回傳0 err 因為z只定義沒有賦值 則nil值為0
    }
    a *= 2
    b *= 3
    z = a + b
    return //回傳 z err
}

func main()  {
    x, y := -1, 2
    z, err := add(x, y)
    if err != nil {
        fmt.Println(err.Error())
        return
    }
    fmt.Printf("add(%d, %d) = %d\n", x, y, z)
}
變長引數
func myfunc(numbers ...int) {
    for _, number := range numbers {
        fmt.Println(number)
    }
}

slice := []int{1, 2, 3, 4, 5}
//使用...將slice打碎傳入
myfunc(slice...)
包與可見性

在 Go 語言中,無論是變數、函式還是類屬性和成員方法,它們的可見性都是以包為維度的,而不是類似傳統面向編程那樣,類屬性和成員方法的可見性封裝在所屬的類中,然后通過 privateprotected 和 public 這些關鍵字來修飾其可見性,

Go 語言沒有提供這些關鍵字,不管是變數、函式,還是自定義類的屬性和成員方法,它們的可見性都是根據其首字母的大小寫來決定的,如果變數名、屬性名、函式名或方法名首字母大寫,就可以在包外直接訪問這些變數、屬性、函式和方法,否則只能在包內訪問,因此 Go 語言類屬性和成員方法的可見性都是包一級的,而不是類一級的,

假如說一個名為domain的檔案夾下有3個.go檔案,則三個檔案中的package都應為domain,其中程式的入口main方法所在的檔案,包為main

//定義了此檔案屬于 main 包
package main

//通過import匯入標注庫中包
import "fmt"

func main() {
	fmt.Println("Hello World!")// Hello World!
    fmt.Println(add(3, 5)) //8
    var sum = add(3, 5)
}

func add(a int, b int) int{
    return a+b;
}

1.1.5 指標

對于學過C語言來說,指標還是比較熟悉的,我所理解的指標,其實就是一個在記憶體中實際的16進制的地址值,參考變數的值通過此地址去記憶體中取出對應的真實值,

func main() {
    i := 0
    //使用&來傳入地址
    fmt.Println(&i) //0xc00000c054
    
    var a, b int = 3 ,4
    //傳入 0xc00000a089 0xc00000a090
    fmt.Println(add(&a, &b)) 
}

//使用*來宣告一個指標型別的引數與使用指標
func add(a *int, b *int)int{
    //接收到 0xc00000a089 0xc00000a090
    //前往 0xc00000a089位置查找具體資料 并取賦給x
    x := *a
    //前往 0xc00000a090位置查找具體資料 并取賦給y
    y := *b
	return x+y
}

1.2 條件、回圈、分支

1.2.1 條件

與Java語言的if基本相同

// if
if condition { 
    // do something 
}

// if...else...
if condition { 
    // do something 
} else {
    // do something 
}

// if...else if...else...
if condition1 { 
    // do something 
} else if condition2 {
    // do something else 
} else {
    // catch-all or default 
}

1.2.2 回圈

sum := 0 

//普通for回圈
for i := 1; i <= 100; i++ { 
    sum += i 
}

//無限回圈
for{
    sum++
    if sum = 100{
        break;
    }
}

//帶條件的回圈
for res := sum+1; sum < 15{
    sum++
    res++
}

//使用kv回圈一個map或一個陣列  k為索引或鍵值 v為值 k、v不需要時可以用_帶替
for k, v := range a {
    fmt.Println(k, v)
}

1.2.3 分支

score := 100
switch score {
case 90, 100:
    fmt.Println("Grade: A")
case 80:
    fmt.Println("Grade: B")
case 70:
    fmt.Println("Grade: C")
case 65:
    fmt.Println("Grade: D")
default:
    fmt.Println("Grade: F")
}

1.3 陣列、切片、字典

1.3.1 陣列

陣列功能與Java語言類似,都是長度不可變,并且可以使用多維陣列,也可以通過arrays[i]來存盤或獲取值,

//宣告
var nums [3]int 
//宣告并初始化
var nums = [3]int{1,2,3} <==> nums:=[3]int{1,2,3}

//使用
for sum := 0, i := 0;i<10{
	sum += nums[i]
	i++
}
//修改值
num[0] = -1

陣列使用較為簡單,但是存在著難以解決的問題:長度固定 ,

例如當我們在程式中需要一個資料結構來存盤獲取到的所有用戶,因為用戶數量是會隨著時間變化的,但是陣列其長度卻不可改變,所以陣列并不適合存盤長度會發生改變的資料,因此在Go語言中通過使用切片來解決以上問題,

1.3.2 切片

切片相比于Java來說是一種全新的概念,在Java中,對于不定長的資料存盤結構,可以使用List介面來完成操作,例如有ArrayList與LinkList,這些介面可以實作資料的隨時添加與獲取,并沒有對長度進行限制,但是在Go中不存在這樣的介面,而是通過切片(Slice)來完成不定長的資料長度存盤

切片與陣列最大的不同就是切片不用宣告長度,但是切片與陣列并非毫無關系,陣列可以看作是切片的底層陣列,而切片則可以看作是陣列某個連續片段的參考,切片可以只使用陣列的一部分元素或者整個陣列來創建,甚至可以創建一個比所基于的陣列還要大的切片:

長度、容量

切片的長度就是它所包含的元素個數,

切片的容量是從它的第一個元素開始數,到其底層陣列元素末尾的個數,

切片 s 的長度和容量可通過運算式 len(s) 和 cap(s) 來獲取,

切片的長度從功能上類比與Java中List的size(),即通過len(slice)來感知切片的長度,即可對len(slice)進行回圈,來動態控制切片內的具體內容,切片的容量在實際開發中運用不多,了解其概念即可,

創建切片
//宣告一個陣列
var nums =[3]int{1, 2, 3}
//0.直接宣告
var slice =[]int{0, 1, 2}

//1.從陣列中參考切片 其中a:b是指包括a但不包括b
var slice1 = nums[0:2] //{1,2}
//如果不寫的則默認為0(左邊)或最大值(右邊)
var slice2 = slice1[:2] <==> var slice2 = slice1[0:] <==>var slice2 = slice1[:]

//2.使用make創建Slice 其中int為切片型別,4為其長度,5為容量
slice3 := make([]int, 5)
slice4 := make([]int, 4, 5)
動態操作切片
//使用append向切片中動態的添加元素
func append(s []T, vs ...T) []T

slice5 := make([]int, 4, 5) //{0, 0, 0, 0}
slice5 = append(slice5, 1) //{0,0,0,0,1}

//洗掉第一個0
sliece5 = slice5[1:]
切片的常用場景

模擬上述提到的問題使用切片解決方案

//宣告切片
var userIds = []int{}
//模擬獲取所有用戶ID
for i := 0; i< 100{
    userIds = append(userIdS, i);
    i++;
}
//對用戶資訊進行處理
for k,v := range userIds{
    userIds[k] = v++
}

1.3.3 字典

字典也可稱為 ‘鍵值對’ 或 ‘key-value’,是一種常用的資料結構,Java中有各種Map介面,常用的有HashMap等,在Go中通過使用字典來實作鍵值對的存盤,字典是無序的,所以不會根據添加順序來保證資料的順序,

字典的宣告與初始化
//string為鍵型別,int為值型別
maps := map[string]int{
  "java" : 1,
  "go" : 2,
  "python" : 3,
}

//還可以通過make來創建字典 100為其初始容量 超出可擴容
maps = make(map[string]int, 100)
字典的使用場景
//直接使用
fmt.Println(maps["java"]) //1

//賦值
maps["go"] = 4

//取值 同時判斷map中是否存在該鍵 ok為bool型
value, ok := maps["one"] 
if ok { // 找到了
  // 處理找到的value 
}

//洗掉
delete(testMap, "four")

二、面向物件編程

2.1 Go語言中的類

眾所周知,在面向物件的語言中,一個類應該具有屬性、構造方法、成員方法三種結構,Go語言也不例外,

2.1.1 類的宣告與初始化

Go語言中并沒有明確的類的概念,只有struct關鍵字可以從功能上類比為 面向物件語言中的“類” ,比如要定義一個學生類,可以這么做:

type Student struct {
    id int
    name string
    male bool
    score float64
}//定義了一個學生類,屬性有id name等,每個屬性的型別都在其后面

//定義學生類的構造方法
func NewStudent(id uint, name string, male bool, score float64) *Student {
    return &Student{id, name, male, score}
}

//實體化一個類物件
student := NewStudent(1, "學院君", 100)
fmt.Println(student)

2.1.2 成員方法

Go中的成員方法宣告與其他語言不大相同,以Student類為例,

//在方法名前,添加對應的類,即可認為改方法為該類的成員方法,
func (s Student) GetName() string  {
    return s.name
}

//注意這里的Student是帶了*的 這是因為在方法傳值程序中 存在著值傳遞與參考傳遞 即指標的概念 當使用值傳遞時 編譯器會為該引數創建一個副本傳入 因此如果對副本進行修改其實是不生效的 因為在執行完此方法后該副本會被銷毀 所以此處應該是用*Student 將要修改的物件指標傳入 修改值才能起作用
func (s *Student) SetName(name string) {
    //這里其實是應該使用(*s).name = name,因為對于一個地址來說 其屬性是沒意義的 不過這樣使用也是可以的 因為編譯器會幫我們自動轉換
    s.name = name
}

2.2 介面

介面在 Go 語言中有著至關重要的地位,如果說 goroutine 和 channel 是支撐起 Go 語言并發模型的基石,那么介面就是 Go 語言整個型別系統的基石,Go 語言的介面不單單只是介面,下面就讓我們一步步來探索 Go 語言的介面特性,

2.2.1 傳統侵入式介面實作

和類的實作相似,Go 語言的介面和其他語言中提供的介面概念完全不同,以 Java、PHP 為例,介面主要作為不同類之間的契約(Contract)存在,對契約的實作是強制的,體現在具體的細節上就是如果一個類實作了某個介面,就必須實作該介面宣告的所有方法,這個叫「履行契約」:

// 宣告一個'iTemplate'介面
interface iTemplate
{
    public function setVariable($name, $var);
    public function getHtml($template);
}


// 實作介面
// 下面的寫法是正確的
class Template implements iTemplate
{
    private $vars = array();

    public function setVariable($name, $var)
    {
        $this->vars[$name] = $var;
    }

    public function getHtml($template)
    {
        foreach($this->vars as $name => $value) {
            $template = str_replace('{' . $name . '}', $value, $template);
        }

        return $template;
    }
}

這個時候,如果有另外有一個介面 iTemplate2 宣告了與 iTemplate 完全一樣的介面方法,甚至名字也叫 iTemplate,只不過位于不同的命名空間下,編譯器也會認為上面的類 Template 只實作了 iTemplate 而沒有實作 iTemplate2 介面,

這在我們之前的認知中是理所當然的,無論是類與類之間的繼承,還是類與介面之間的實作,在 Java、PHP 這種單繼承語言中,存在著嚴格的層級關系,一個類只能直接繼承自一個父類,一個類也只能實作指定的介面,如果沒有顯式宣告繼承自某個父類或者實作某個介面,那么這個類就與該父類或者該介面沒有任何關系,

我們把這種介面稱為侵入式介面,所謂「侵入式」指的是實作類必須明確宣告自己實作了某個介面,這種實作方式雖然足夠明確和簡單明了,但也存在一些問題,尤其是在設計標準庫的時候,因為標準庫必然涉及到介面設計,介面的需求方是業務實作類,只有具體撰寫業務實作類的時候才知道需要定義哪些方法,而在此之前,標準庫的介面就已經設計好了,我們要么按照約定好的介面進行實作,如果沒有合適的介面需要自己去設計,這里的問題就是介面的設計和業務的實作是分離的,介面的設計者并不能總是預判到業務方要實作哪些功能,這就造成了設計與實作的脫節,

介面的過分設計會導致某些宣告的方法實作類完全不需要,如果設計的太簡單又會導致無法滿足業務的需求,這確實是一個問題,而且脫離了用戶使用場景討論這些并沒有意義,以 PHP 自帶的 SessionHandlerInterface 介面為例,該介面宣告的介面方法如下:

SessionHandlerInterface {
    /* 方法 */
    abstract public close ( void ) : bool
    abstract public destroy ( string $session_id ) : bool
    abstract public gc ( int $maxlifetime ) : int
    abstract public open ( string $save_path , string $session_name ) : bool
    abstract public read ( string $session_id ) : string
    abstract public write ( string $session_id , string $session_data ) : bool
}

用戶自定義的 Session 管理器需要實作該介面,也就是要實作該介面宣告的所有方法,但是實際在做業務開發的時候,某些方法其實并不需要實作,比如如果我們基于 Redis 或 Memcached 作為 Session 存盤器的話,它們自身就包含了過期回識訓制,所以 gc 方法根本不需要實作,又比如 close 方法對于大部分驅動來說,也是沒有什么意義的,

正是因為這種不合理的設計,所以在撰寫 PHP 類別庫中的每個介面時都需要糾結以下兩個問題(Java 也類似):

  1. 一個介面需要宣告哪些介面方法?
  2. 如果多個類實作了相同的介面方法,應該如何設計介面?比如上面這個 SessionHandlerInterface,有沒有必要拆分成多個更細分的介面,以適應不同實作類的需要?

接下我們來看看 Go 語言的介面是如何避免這些問題的,

2.2.2 Go 語言的介面實作

在 Go 語言中,類對介面的實作和子類對父類的繼承一樣,并沒有提供類似 implement 這種關鍵字顯式宣告該類實作了哪個介面,一個類只要實作了某個介面要求的所有方法,我們就說這個類實作了該介面

例如,我們定義了一個 File 類,并實作了 Read()Write()Seek()Close() 四個方法:

type File struct { 
    // ...
}

func (f *File) Read(buf []byte) (n int, err error) 
func (f *File) Write(buf []byte) (n int, err error) 
func (f *File) Seek(off int64, whence int) (pos int64, err error) 
func (f *File) Close() error

假設我們有如下介面(Go 語言通過關鍵字 interface 來宣告介面,以示和結構體型別的區別,花括號內包含的是待實作的方法集合):

type IFile interface { 
    Read(buf []byte) (n int, err error) 
    Write(buf []byte) (n int, err error) 
    Seek(off int64, whence int) (pos int64, err error) 
    Close() error 
}

type IReader interface { 
    Read(buf []byte) (n int, err error) 
}

type IWriter interface { 
    Write(buf []byte) (n int, err error) 
}

type ICloser interface { 
    Close() error 
}

盡管 File 類并沒有顯式實作這些介面,甚至根本不知道這些介面的存在,但是我們說 File 類實作了這些介面,因為 File 類實作了上述所有介面宣告的方法,當一個類的成員方法集合包含了某個介面宣告的所有方法,換句話說,如果一個介面的方法集合是某個類成員方法集合的子集,我們就認為該類實作了這個介面,

與 Java、PHP 相對,我們把 Go 語言的這種介面稱作非侵入式介面,因為類與介面的實作關系不是通過顯式宣告,而是系統根據兩者的方法集合進行判斷,這樣做有兩個好處:

  • 其一,Go 語言的標準庫不需要繪制類別庫的繼承/實作樹圖,在 Go 語言中,類的繼承樹并無意義,你只需要知道這個類實作了哪些方法,每個方法是干什么的就足夠了,
  • 其二,定義介面的時候,只需要關心自己應該提供哪些方法即可,不用再糾結介面需要拆得多細才合理,也不需要為了實作某個介面而引入介面所在的包,介面由使用方按需定義,不用事先設計,也不用考慮之前是否有其他模塊定義過類似介面,

這樣一來,就完美地避免了傳統面向物件編程中的介面設計問題,

三、并發與多執行緒

3.1 Goroutine

對于任何一個優秀的語言來說,并發處理的能力都是決定其優劣的關鍵,在Go語言中,通過Goroutine來實作并發的處理,

func say(s string) {
	fmt.Println(s)
}

func main() {
    //通過 go 關鍵字新開一個協程
	go say("world")
	say("hello")
}

Go語言中沒有像Java那么多的鎖來限制資源同時訪問,只提供了Mutex來進行同步操作,

//給類SafeCounter添加鎖
type SafeCounter struct {
	v   map[string]int
	mux sync.Mutex
}

// Inc 增加給定 key 的計數器的值,
func (c *SafeCounter) Inc(key string) {
    //給該物件上鎖
	c.mux.Lock()
	// Lock 之后同一時刻只有一個 goroutine 能訪問 c.v
	c.v[key]++
    //解鎖
	c.mux.Unlock()
}

3.2 Channel

多協程之間通過Channel進行通信,從功能上可以類比為Java的volatile關鍵字,

ch := make(chan int) 宣告一個int型的Channel,兩個協程之間可以通過ch進行int資料通信,

通過Channel進行資料傳輸,

ch <- v    // 將 v 發送至信道 ch,
v := <-ch  // 從 ch 接收值并賦予 v,
package main

import "fmt"

func sum(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	c <- sum // 將和送入 c
}

//對于main方法來說 相當于就是開啟了一個協程
func main() {
	s := []int{7, 2, 8, -9, 4, 0}

	c := make(chan int)
    //通過go關鍵字開啟兩個協程 將chaneel當做引數傳入
	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)
    //通過箭頭方向獲取或傳入資訊
	x, y := <-c, <-c // 從 c 中接收

	fmt.Println(x, y, x+y)
}

四、錯誤處理

4.1 error

Go 語言錯誤處理機制非常簡單明了,不需要學習了解復雜的概念、函式和型別,Go 語言為錯誤處理定義了一個標準模式,即 error 介面,該介面的定義非常簡單:

type error interface { 
    Error() string 
}

其中只宣告了一個 Error() 方法,用于回傳字串型別的錯誤訊息,對于大多數函式或類方法,如果要回傳錯誤,基本都可以定義成如下模式 —— 將錯誤型別作為第二個引數回傳:

func Foo(param int) (n int, err error) { 
    // ...
}

然后在呼叫回傳錯誤資訊的函式/方法時,按照如下「衛述陳述句」模板撰寫處理代碼即可:

n, err := Foo(0)

if err != nil { 
    // 錯誤處理 
} else{
    // 使用回傳值 n 
}

非常簡潔優雅,

4.2 defer

defer用于確保一個方法執行完成之后,無論執行結果是否成功,都要執行defer中的陳述句,類似于Java中的try..catch..finally用法,例如在檔案處理中,無論結果是否成功,都要關閉檔案流,

func ReadFile(filename string) ([]byte, error) {
    f, err := os.Open(filename)
    if err != nil {
        return nil, err
    }
    //無論結果如何 都要關閉檔案流
    defer f.Close()

    var n int64 = bytes.MinRead

    if fi, err := f.Stat(); err == nil {
        if size := fi.Size() + bytes.MinRead; size > n {
            n = size
        }
    }
    return readAll(f, n)
}

4.3 panic

Go語言中沒有太多的例外類,不像Java一樣有Error、Exception等錯誤型別,當然也沒有try..catch陳述句,

Panic(恐慌),意味在程式運行中出現了錯誤,如果該錯誤未被捕獲的話,就會造成系統崩潰退出,例如一個簡單的panic:a := 1/0

就會引發panic: integer divide by zero,

-w926

其中第一行表示出問題的協程,第二行是問題代碼所在的包和函式,第三行是問題代碼的具體位置,最后一行則是程式的退出狀態,通過這些資訊,可以幫助你快速定位問題并予以解決,

4.4 recover

當有可以預見的錯誤時,又不希望程式崩潰退出,可以使用recover()陳述句來捕獲未處理的panic,recover應當放在defer陳述句中,且該陳述句應該在方法中前部,避免未能執行到defer陳述句時就引發了系統例外退出,

package main

import (
    "fmt"
)

func divide() {
    //通過defer,確保該方法只要執行完畢都要執行該匿名方法
    defer func() {
        //進行例外捕獲
        if err := recover(); err != nil {
            fmt.Printf("Runtime panic caught: %v\n", err)
        }
    }()

    var i = 1
    var j = 0
    k := i / j
    fmt.Printf("%d / %d = %d\n", i, j, k)
}

func main() {
    divide()
    fmt.Println("divide 方法呼叫完畢,回到 main 函式")
}

-w747

可以看到,雖然會出現例外,但我們使用recover()捕獲之后,就不會出現系統崩潰退出的情形,而只是將該方法結束,其中fmt.Printf("%d / %d = %d\n", i, j, k)陳述句并沒有執行到,因為代碼執行到他的上一步已經出現例外導致該方法提前結束,
4 recover

當有可以預見的錯誤時,又不希望程式崩潰退出,可以使用recover()陳述句來捕獲未處理的panic,recover應當放在defer陳述句中,且該陳述句應該在方法中前部,避免未能執行到defer陳述句時就引發了系統例外退出,

package main

import (
    "fmt"
)

func divide() {
    //通過defer,確保該方法只要執行完畢都要執行該匿名方法
    defer func() {
        //進行例外捕獲
        if err := recover(); err != nil {
            fmt.Printf("Runtime panic caught: %v\n", err)
        }
    }()

    var i = 1
    var j = 0
    k := i / j
    fmt.Printf("%d / %d = %d\n", i, j, k)
}

func main() {
    divide()
    fmt.Println("divide 方法呼叫完畢,回到 main 函式")
}

可以看到,雖然會出現例外,但我們使用recover()捕獲之后,就不會出現系統崩潰退出的情形,而只是將該方法結束,其中fmt.Printf("%d / %d = %d\n", i, j, k)陳述句并沒有執行到,因為代碼執行到他的上一步已經出現例外導致該方法提前結束,

五、總結

通過以上的學習,大家可以以使用為目的的初步了解到go的基礎語法,但是僅憑本文想要學明白go是完全不夠的,例如go的最大優勢之一“協程”,由于文章目的就沒有特別詳細展開,有興趣的同學可以繼續學習,

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

標籤:Java

上一篇:這些JDK8 新特性,我還是第一次聽說

下一篇:Seata分布式事務 (理論與部署相結合)

標籤雲
其他(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