Java程序员的Golang入门指南

1.序言

Golang作为一门出身名门望族的编程语言新星,像豆瓣的Redis平台Codis、类Evernote的云笔记leanote等。

1.1 为什么要学习

如果有人说X语言比Y语言好,两方的支持者经常会激烈地争吵。如果你是某种语言老手,你就是那门语言的“传道者”,下意识地会保护它。无论承认与否,你都已被困在一个隧道里,你看到的完全是局限的。《肖申克的救赎》对此有很好的注脚:

[Red] These walls are funny. First you hate ‘em, then you get used to ‘em. Enough time passes, you get so you depend on them. That’s institutionalized.

这些墙很有趣。起初你恨它们,之后你习惯了它们。随着时间流逝,你开始以来它们。这就是体制。

在你还没有被完全“体制化”时,为何不多学些语言,哪怕只是浅尝辄止,潜移默化中也许你的思维壁垒就松动了。不管是Golang还是Ruby还是其他语言,当看到一些语法习惯与之前熟悉的C和Java不同时,的确潜意识里就会产生抵触情绪,觉得这不好,还是自己习惯的那套好。长此以往,如果不能冲破自己的心理,“坐以待毙”,被时间淘汰恐怕只是早晚的事儿。所以这里的关键也 不是非要学习Golang,而是要不断地学!

1.2 豪华的开发团队

(略)

1.3 用什么工具来开发

Golang也有专门的IDE,但由于最近迷上了Sublime Text神器,所以这里还是用ST来学习Golang。配置步骤与在ST中使用其他语言开发都类似:

  1. 安装智能提示插件GoSublime
  2. 创建编译配置脚本

点Preferences -> Package Settings -> GoSublime -> User Settings中写入(感觉保存时自动格式化出来的缩进、空格等风格有些“讨厌”,所以就禁掉了):

  1. {
  2. "fmt_enabled": false,
  3. "env": {
  4. "path":"D:\\Program Files (x86)\\Go\bin"
  5. }
  6. }

点新建Build System产生go.sublime-build中写入:

  1. {
  2. "path": "D:\\Program Files (x86)\\Go\\bin",
  3. "cmd": ["go", "run", "${file}"],
  4. "selector": "source.go"
  5. }

2.你好,世界

Golang版的HelloWorld来了!一眼望去,package和import的声明方式与Java如出一辙,比较明显的区别是:func关键字、每行末尾没有分号、Println()大写的函数名。这个例子虽小,却“五脏俱全”,后面会逐一分析这个小例子中碰到的Golang语法点。

  1. package main
  2. import "fmt"
  3. func main() {
  4. fmt.Println("你好,世界!")
  5. }

2.1 运行方式

Golang提供了go run“解释”执行和go build编译执行两种运行方式,所谓的“解释”执行其实也是编译出了可执行文件后才执行的。

  1. $ go run helloworld.go
  2. 你好,世界!
  3. $ go build helloworld.go
  4. $ ls
  5. helloworld helloworld.go
  6. $ ./helloworld
  7. 你好,世界!

2.2 Package管理

上面例子中我们使用的就是fmt包下的Println()函数。Golang约定:我们可以用./或../相对路径来引自己的package;如果不是相对路径,那么go会去$GOPATH/src下查找。

2.3 格式化输出

类似C、Java等语言,Golang的fmt包提供了格式化输出功能,而且像%d、%s等占位符和\t、\r、\n转义也几乎完全一致。但Golang的Println不支持格式化,只有Printf支持,所以我们经常会在后面加入\n换行。此外,Golang加入了%T打印值的类型,%v打印数组等集合的所有元素。

  1. package main
  2. import "fmt"
  3. import "math"
  4. /**
  5. * This is Printer!
  6. * 布尔值:false
  7. * 二进制:11111111
  8. * 八进制:377
  9. * 十六进制:FF
  10. * 十进制:255
  11. * 浮点数:3.141593
  12. * 字符串:printer
  13. *
  14. * 对象类型:int,string,bool,float64
  15. * 集合:[1 2 3 4 5]
  16. */
  17. func main() {
  18. fmt.Println("This is Printer!")
  19. fmt.Printf("布尔值:%t\n", 1 == 2)
  20. fmt.Printf("二进制:%b\n", 255)
  21. fmt.Printf("八进制:%o\n", 255)
  22. fmt.Printf("十六进制:%X\n", 255)
  23. fmt.Printf("十进制:%d\n", 255)
  24. fmt.Printf("浮点数:%f\n", math.Pi)
  25. fmt.Printf("字符串:%s\n", "printer")
  26. fmt.Printf("对象类型:%T,%T,%T,%T\n", 1, "hello", true, math.E)
  27. fmt.Printf("集合:%v\n", [5]int{1, 2, 3, 4, 5})
  28. }

3.语法基础

3.1 变量和常量

虽然Golang是静态类型语言,却用类似JavaScript中的var关键字声明变量。而且像同样是静态语言的Scala一样,支持类型自动推断。有一点很重要的不同是:如果明确指明变量类型的话,类型要放在变量名后面。这有点别扭吧?!后面会看到函数的入参和返回值的类型也要这样声明。

  1. package main
  2. import "fmt"
  3. /**
  4. * 单变量声明:num[100], word[hello]
  5. * 多变量声明:i[1], i[2], k[3]
  6. * 推导类型:b1[true], b2[false]
  7. * 常量:age[20], pi[3.141593]
  8. */
  9. func main() {
  10. var num int = 100
  11. var word string = "hello"
  12. fmt.Printf("单变量声明:num[%d], word[%s]\n", num, word)
  13. var i, j, k int = 1, 2, 3
  14. fmt.Printf("多变量声明:i[%d], i[%d], k[%d]\n", i, j, k)
  15. var b1 = true
  16. b2 := false
  17. fmt.Printf("推导类型:b1[%t], b2[%t]\n", b1, b2)
  18. const age int = 20
  19. const pi float32 = 3.1415926
  20. fmt.Printf("常量:age[%d], pi[%f]\n", age, pi)
  21. }

3.2 控制语句

作为最基本的语法要素,Golang的各种控制语句也是特点鲜明。在对C继承发扬的同时,也有自己的想法融入其中:

  • if/switch/for的条件部分都没有圆括号,但必须有花括号。
  • switch的case中不需要break。《C专家编程》里也“控诉”了C的fall-through问题。既然90%以上的情况都要break,为何不将break作为case的默认行为?而且编程语言后来者也鲜有纠正这一问题的。
  • switch的case条件可以是多个值
  • Golang中没有while
  1. package main
  2. import "fmt"
  3. /**
  4. * testIf: x[2] is even
  5. * testIf: x[3] is odd
  6. *
  7. * testSwitch: One
  8. * testSwitch: Two
  9. * testSwitch: Three, Four, Five [3]
  10. * testSwitch: Three, Four, Five [4]
  11. * testSwitch: Three, Four, Five [5]
  12. *
  13. * 标准模式:[0] [1] [2] [3] [4] [5] [6]
  14. * While模式:[0] [1] [2] [3] [4] [5] [6]
  15. * 死循环模式:[0] [1] [2] [3] [4] [5] [6]
  16. */
  17. func main() {
  18. testIf(2)
  19. testIf(3)
  20. testSwitch(1)
  21. testSwitch(2)
  22. testSwitch(3)
  23. testSwitch(4)
  24. testSwitch(5)
  25. testFor(7)
  26. }
  27. func testIf(x int) {
  28. if x % 2 == 0 {
  29. fmt.Printf("testIf: x[%d] is even\n", x)
  30. } else {
  31. fmt.Printf("testIf: x[%d] is odd\n", x)
  32. }
  33. }
  34. func testSwitch(i int) {
  35. switch i {
  36. case 1:
  37. fmt.Println("testSwitch: One")
  38. case 2:
  39. fmt.Println("testSwitch: Two")
  40. case 3, 4, 5:
  41. fmt.Printf("testSwitch: Three, Four, Five [%d]\n", i)
  42. default:
  43. fmt.Printf("testSwitch: Invalid value[%d]\n", i)
  44. }
  45. }
  46. func testFor(upper int) {
  47. fmt.Print("标准模式:")
  48. for i := 0; i < upper; i++ {
  49. fmt.Printf("[%d] ", i)
  50. }
  51. fmt.Println()
  52. fmt.Print("While模式:")
  53. j := 0
  54. for j < upper {
  55. fmt.Printf("[%d] ", j)
  56. j++
  57. }
  58. fmt.Println()
  59. fmt.Print("死循环模式:")
  60. k := 0
  61. for {
  62. if (k >= upper) {
  63. break
  64. }
  65. fmt.Printf("[%d] ", k)
  66. k++
  67. }
  68. fmt.Println()
  69. }

分号和花括号

分号由词法分析器在扫描源代码过程自动插入的,分析器使用简单的规则:如果在一个新行前方的最后一个标记是一个标识符(包括像int和float64这样的单词)、一个基本的如数值这样的文字、或break continue fallthrough return ++ – ) }中的一个时,它就会自动插入分号。

分号的自动插入规则产生了“蝴蝶效应”:所有控制结构的左花括号不都能放在下一行。因为按照上面的规则,这样做会导致分析器在左花括号的前方插入一个分号,从而引起难以预料的结果。所以Golang中是不能随便换行的

3.3 函数

函数有几点不同:

  • func关键字。
  • 最大的不同就是“倒序”的类型声明
  • 不需要函数原型,引用的函数可以后定义。这一点很好,真不喜欢C语言里要么将“最底层抽象”的函数放在最前面定义,要么写一堆函数原型声明在最前面。

3.4 集合

Golang提供了数组和Map作为基本数据结构:

  • 数组中的元素会自动初始化,例如int数组元素初始化为0
  • 切片(借鉴Python)的区间跟主流语言一样,都是 “左闭右开”
  • range()遍历数组和Map
  1. package main
  2. import "fmt"
  3. /**
  4. * Array未初始化: [0 0 0 0 0]
  5. * Array赋值: [0 10 0 20 0]
  6. * Array初始化: [0 1 2 3 4 5]
  7. * Array二维: [[0 1 2] [1 2 3]]
  8. * Array切片: [2 3] [0 1 2 3] [2 3 4 5]
  9. *
  10. * Map哈希表:map[one:1 two:2 three:3],长度[3]
  11. * Map删除元素后:map[one:1 three:3],长度[2]
  12. * Map打印:
  13. * one => 1
  14. * four => 4
  15. * three => 3
  16. * five => 5
  17. */
  18. func main() {
  19. testArray()
  20. testMap()
  21. }
  22. func testArray() {
  23. var a [5]int
  24. fmt.Println("Array未初始化: ", a)
  25. a[1] = 10
  26. a[3] = 20
  27. fmt.Println("Array赋值: ", a)
  28. b := []int{0, 1, 2, 3, 4, 5}
  29. fmt.Println("Array初始化: ", b)
  30. var c [2][3]int
  31. for i := 0; i < 2; i++ {
  32. for j := 0; j < 3; j++ {
  33. c[i][j] = i + j
  34. }
  35. }
  36. fmt.Println("Array二维: ", c)
  37. d := b[2:4] // b[3,4]
  38. e := b[:4] // b[1,2,3,4]
  39. f := b[2:] // b[3,4,5]
  40. fmt.Println("Array切片:", d, e, f)
  41. }
  42. func testMap() {
  43. m := make(map[string]int)
  44. m["one"] = 1
  45. m["two"] = 2
  46. m["three"] = 3
  47. fmt.Printf("Map哈希表:%v,长度[%d]\n", m, len(m))
  48. delete(m, "two")
  49. fmt.Printf("Map删除元素后:%v,长度[%d]\n", m, len(m))
  50. m["four"] = 4
  51. m["five"] = 5
  52. fmt.Println("Map打印:")
  53. for key, val := range m {
  54. fmt.Printf("\t%s => %d\n", key, val)
  55. }
  56. fmt.Println()
  57. }

3.5 指针和内存分配

Golang中可以使用指针,并提供了两种内存分配机制:

  • new:分配长度为0的空白内存,返回类型T*。
  • make:仅用于 切片、map、chan消息管道,返回类型T而不是指针。
  1. package main
  2. import "fmt"
  3. /**
  4. * 整数i=[10],指针pInt=[0x184000c0],指针指向*pInt=[10]
  5. * 整数i=[3],指针pInt=[0x184000c0],指针指向*pInt=[3]
  6. * 整数i=[5],指针pInt=[0x184000c0],指针指向*pInt=[5]
  7. *
  8. * Wild的数组指针: <nil>
  9. * Wild的数组指针==nil[true]
  10. *
  11. * New分配的数组指针: &[]
  12. * New分配的数组指针[0x18443010],长度[0]
  13. * New分配的数组指针==nil[false]
  14. * New分配的数组指针Make后: &[0 0 0 0 0 0 0 0 0 0]
  15. * New分配的数组元素[3]: 23
  16. *
  17. * Make分配的数组引用: [0 0 0 0 0 0 0 0 0 0]
  18. */
  19. func main() {
  20. testPointer()
  21. testMemAllocate()
  22. }
  23. func testPointer() {
  24. var i int = 10;
  25. var pInt *int = &i;
  26. fmt.Printf("整数i=[%d],指针pInt=[%p],指针指向*pInt=[%d]\n",
  27. i, pInt, *pInt)
  28. *pInt = 3
  29. fmt.Printf("整数i=[%d],指针pInt=[%p],指针指向*pInt=[%d]\n",
  30. i, pInt, *pInt)
  31. i = 5
  32. fmt.Printf("整数i=[%d],指针pInt=[%p],指针指向*pInt=[%d]\n",
  33. i, pInt, *pInt)
  34. }
  35. func testMemAllocate() {
  36. var pNil *[]int
  37. fmt.Println("Wild的数组指针:", pNil)
  38. fmt.Printf("Wild的数组指针==nil[%t]\n", pNil == nil)
  39. var p *[]int = new([]int)
  40. fmt.Println("New分配的数组指针:", p)
  41. fmt.Printf("New分配的数组指针[%p],长度[%d]\n", p, len(*p))
  42. fmt.Printf("New分配的数组指针==nil[%t]\n", p == nil)
  43. //Error occurred
  44. //(*p)[3] = 23
  45. *p = make([]int, 10)
  46. fmt.Println("New分配的数组指针Make后:", p)
  47. (*p)[3] = 23
  48. fmt.Println("New分配的数组元素[3]:", (*p)[3])
  49. var v []int = make([]int, 10)
  50. fmt.Println("Make分配的数组引用:", v)
  51. }

3.6 面向对象编程

Golang的结构体跟C有几点不同:

  • 结构体可以有方法,其实也就相当于OOP中的类了。
  • 支持带名称的初始化。
  • 用指针访问结构中的属性也用”.”而不是”->”,指针就像Java中的引用一样。
  • 没有public,protected,private等访问权限控制。C也没有protected,C中默认是public的,private需要加static关键字限定。Golang中方法名大写就是public的,小写就是private的。

同时,Golang支持接口和多态,而且接口有别于Java中继承和实现的方式,而是采取了类似Ruby中更为新潮的Duck Type。只要struct与interface有相同的方法,就认为struct实现了这个接口。就好比只要能像鸭子那样叫,我们就认为它是一只鸭子一样。

  1. package main
  2. import (
  3. "fmt"
  4. "math"
  5. )
  6. // -----------------
  7. // Struct
  8. // -----------------
  9. type Person struct {
  10. name string
  11. age int
  12. email string
  13. }
  14. func (p *Person) getName() string {
  15. return p.name
  16. }
  17. // -------------------
  18. // Interface
  19. // -------------------
  20. type shape interface {
  21. area() float64
  22. }
  23. type rect struct {
  24. width float64
  25. height float64
  26. }
  27. func (r *rect) area() float64 {
  28. return r.width * r.height
  29. }
  30. type circle struct {
  31. radius float64
  32. }
  33. func (c *circle) area() float64 {
  34. return math.Pi * c.radius * c.radius
  35. }
  36. // -----------------
  37. // Test
  38. // -----------------
  39. /**
  40. * 结构Person[{cdai 30 cdai@gmail.com}],姓名[cdai]
  41. * 结构Person指针[&{cdai 30 cdai@gmail.com}],姓名[cdai]
  42. * 用指针修改结构Person为[{carter 40 cdai@gmail.com}]
  43. *
  44. * Shape[0]周长为[13.920000]
  45. * Shape[1]周长为[58.088048]
  46. */
  47. func main() {
  48. testStruct()
  49. testInterface()
  50. }
  51. func testStruct() {
  52. p1 := Person{"cdai", 30, "cdai@gmail.com"}
  53. p1 = Person{name: "cdai", age: 30, email: "cdai@gmail.com"}
  54. fmt.Printf("结构Person[%v],姓名[%s]\n", p1, p1.getName())
  55. ptr1 := &p1
  56. fmt.Printf("结构Person指针[%v],姓名[%s]\n", ptr1, ptr1.getName())
  57. ptr1.age = 40
  58. ptr1.name = "carter"
  59. fmt.Printf("用指针修改结构Person为[%v]\n", p1)
  60. }
  61. func testInterface() {
  62. r := rect { width: 2.9, height: 4.8 }
  63. c := circle { radius: 4.3 }
  64. s := []shape{ &r, &c }
  65. for i, sh := range s {
  66. fmt.Printf("Shape[%d]周长为[%f]\n", i, sh.area())
  67. }
  68. }

3.7 异常处理

Golang中异常的使用比较简单,可以用errors.New创建,也可以实现Error接口的方法来自定义异常类型,同时利用函数的多返回值特性可以返回异常类。比较复杂的是defer和recover关键字的使用。Golang没有采取try-catch“包住”可能出错代码的这种方式,而是用 延迟处理 的方式。

用defer调用的函数会以后进先出(LIFO)的方式,在当前函数结束后依次顺行执行。defer的这一特点正好可以用来处理panic。当panic被调用时,它将立即停止当前函数的执行并开始逐级解开函数堆栈,同时运行所有被defer的函数。如果这种解开达到堆栈的顶端,程序就死亡了。但是,也可以使用内建的recover函数来重新获得Go程的控制权并恢复正常的执行。由于仅在解开期间运行的代码处在被defer的函数之内,recover仅在被延期的函数内部才是有用的

  1. package main
  2. import (
  3. "fmt"
  4. "errors"
  5. "os"
  6. )
  7. /**
  8. * 自定义Error类型,实现内建Error接口
  9. * type Error interface {
  10. * Error() string
  11. * }
  12. */
  13. type MyError struct {
  14. arg int
  15. msg string
  16. }
  17. func (e *MyError) Error() string {
  18. return fmt.Sprintf("%d - %s", e.arg, e.msg)
  19. }
  20. /**
  21. * Failed[*errors.errorString]: Bad Arguments - negative!
  22. * Success: 16
  23. * Failed[*main.MyError]: 1000 - Bad Arguments - too large!
  24. *
  25. * Recovered! Panic message[Cannot find specific file]
  26. * 4 3 2 1 0
  27. */
  28. func main() {
  29. // 1.Test error
  30. args := []int{-1, 4, 1000}
  31. for _, i := range args {
  32. if r, e := testError(i); e != nil {
  33. fmt.Printf("Failed[%T]: %v\n", e, e)
  34. } else {
  35. fmt.Println("Success: ", r)
  36. }
  37. }
  38. // 2.Test defer
  39. src, err := os.Open("control.go")
  40. if (err != nil) {
  41. fmt.Printf("打开文件错误[%v]\n", err)
  42. return
  43. }
  44. defer src.Close()
  45. // use src...
  46. for i := 0; i < 5; i++ {
  47. defer fmt.Printf("%d ", i)
  48. }
  49. // 3.Test panic/recover
  50. defer func() {
  51. if r := recover(); r != nil {
  52. fmt.Printf("Recovered! Panic message[%s]\n", r)
  53. }
  54. }()
  55. _, err2 := os.Open("test.go")
  56. if (err2 != nil) {
  57. panic("Cannot find specific file")
  58. }
  59. }
  60. func testError(arg int) (int, error) {
  61. if arg < 0 {
  62. return -1, errors.New("Bad Arguments - negative!")
  63. } else if arg > 256 {
  64. return -1, &MyError{ arg, "Bad Arguments - too large!" }
  65. } else {
  66. return arg * arg, nil
  67. }
  68. }

Java程序员的Golang入门指南(上)的更多相关文章

  1. Java程序员的Golang入门指南(下)

    Java程序员的Golang入门指南(下) 4.高级特性 上面介绍的只是Golang的基本语法和特性,尽管像控制语句的条件不用圆括号.函数多返回值.switch-case默认break.函数闭包.集合 ...

  2. Java程序员的现代RPC指南

    Java程序员的现代RPC指南 1.前言 1.1 RPC框架简介 最早接触RPC还是初学Java时,直接用Socket API传东西好麻烦.于是发现了JDK直接支持的RMI,然后就用得不亦乐乎,各种大 ...

  3. Java程序员的现代RPC指南(Windows版预编译好的Protoc支持C++,Java,Python三种最常用的语言,Thrift则支持几乎主流的各种语言)

    Java程序员的现代RPC指南 1.前言 1.1 RPC框架简介 最早接触RPC还是初学Java时,直接用Socket API传东西好麻烦.于是发现了JDK直接支持的RMI,然后就用得不亦乐乎,各种大 ...

  4. 一名资深架构师规划Java程序员五年职业生涯指南

    每个程序员.或者说每个工作者都应该有自己的职业规划,如果你不是富二代,不是官二代,也没有职业规划,希望你可以思考一下自己的将来.今天我给大家分享的是一篇来自阿里大牛对五年工作经验程序员的职业建议,希望 ...

  5. 2019最新版Java程序员零基础入门视频教程资料(全套)

    为了解决Java学习初学者在网上找视频难的事情,本人整理了一份2019年度最新版的Java学习视频教程.希望看到这份视频的你们都能找到一份称心的工作,技术上都能得到进一步的提升,好东西就要分享给你们, ...

  6. 2017年 Java 程序员,风光背后的危机

    不得不承认,经历过行业的飞速发展期,互联网的整体发展趋于平稳.为什么这么说?为什么要放在 Java 程序员的盘点下说? 的确,对于进可攻前端,后可守后端大本营的 Java 程序员而言,虽然供应逐年上涨 ...

  7. 为 Java 程序员准备的 Go 入门 PPT

    为 Java 程序员准备的 Go 入门 PPT 这是 Google 的 Go 团队技术主管经理 Sameer Ajmani 分享的 PPT,为 Java 程序员快速入门 Go 而准备的. 视频 这个 ...

  8. Java程序员快速入门Go语言

    这篇文章帮助Java程序员快速入门Go语言. 转载至 开源中国社区. http://www.oschina.net 本文将以一个有代表性的例子为开始,以此让Java程序员对Go语言有个初步认识,随后将 ...

  9. Efficient&Elegant:Java程序员入门Cpp

    最近项目急需C++ 的知识结构,虽说我有过快速学习很多新语言的经验,但对于C++ 老特工我还需保持敬畏(内容太多),本文会从一个Java程序员的角度,制定高效学习路线快速入门C++ . Java是为了 ...

随机推荐

  1. enumerate给列表加索引

    >>> list = ['a','b','c'] >>> for i,j in enumerate(list): print(i,j) 0 a 1 b 2 c &g ...

  2. MySQL中的字符串函数

    使用字符串数据 当使用字符串数据时,可以使用下面的字符数据类型. CHAR 固定长度.不足部分使用空格填充的字符串. varchar 变长字符串. text(MySQL和SQL Server)或CLO ...

  3. lvs 负载均衡 NAT模式

    1.原理 基于NAT机制实现.当用户请求到达director之后,director将请求报文的目标地址(即VIP)改成选定的realserver地址,同时将报文的目标端口也改成选定的realserve ...

  4. Java面试题—初级(5)

    41.a.hashCode() 有什么用?与 a.equals(b) 有什么关系? hashCode() 方法对应对象整型的 hash 值.它常用于基于 hash 的集合类,如 Hashtable.H ...

  5. python 函数“四剑客”的使用和介绍

    python函数四剑客:lambda.map.filter和reduce. 一.lambda(匿名函数) 1. 学习lambda要注意一下几点: lambda语句被用来创建新的函数对象,并且在运行的时 ...

  6. CSS3和H5的新特性

    H5的新特性 1.   用于绘画 canvas 元素. 2.   用于媒介回放的 video 和 audio 元素. 3.   本地离线存储 localStorage 长期存储数据,浏览器关闭后数据不 ...

  7. laravel 5.5 接入蚂蚁金服官方SDK(支付宝APP支付为例)开发步骤

    一.创建应用及配置 首先需要到蚂蚁金服开放平台(open.alipay.com)注册应用,获取应用id(APP_ID),并且配置应用,主要是签约应用,这个需要审核,一般2-5个工作日,审核通过后,去生 ...

  8. jQuery滚动指定位置

    $(document).ready(function() { $("#scroll").click(function() { $('html, body').animate({ s ...

  9. testng中使用reportng报告

    1.pom.xml文件中添加依赖,重构一下项目(mvn compile) <dependency> <groupId>org.uncommons</groupId> ...

  10. ●CodeForces 549F Yura and Developers

    题链: http://codeforces.com/problemset/problem/549/F题解: 分治,链表. 考虑对于一个区间[L,R],其最大值在p位置, 那么答案的贡献就可以分为3部分 ...