目录

语言简介

初识 Go 程序

Go 词法单元

变量和常量

复合数据类型


语言简介

已经有那么多种编程语言了,为什么还要发明新语言?为什么还要去学习新语言?相信不少人都有这样的疑问。答案很简单,虽然有那么多种语言,但每种语言都有其独特的应用领域,在某个领域使用某种语言能达到收益/投入的最大化。比如在嵌入式领域,汇编和 C 是首选;在操作系统领域,C 是首选;在系统级服务编程领域,C++ 是首选;在企业级应用程序和 Web 应用领域,Java 是首选。就好比木工的工具箱中锤子可以有很多种,大厨的工具箱中刀子有很多种一样,某种语言就像某种锤子或者某种刀一样,有其特别应用的领域 。

Go 语言的诞生主要基于如下原因 :

(1) 摩尔定律接近失效后多核服务器己经成为主流,当前的编程语言对并发的支持不是很好,不能很好地发挥多核 CPU 的威力。

(2) 程序规模越来越大,编译速度越来越慢,如何快速地编译程序是程序员的迫切需求。

(3) 现有的编程语言设计越来越复杂,由于历史的包袱,某些特性的实现不怎么优雅,程序员花费了更多的精力来应对编程语法细节而不是问题域。

Go 语言就是为了解决当下编程语言对 并发支持不友好编译速度慢编程复杂 这三个问题而诞生的 。

初识 Go 程序

package mainimport "fmt"func main() {/* 这是我的第一个简单的程序 */fmt.Println("Hello, World!")
}

Linux环境下编译 go 程序的方法:go build asd.go,然后使用 ./asd 运行相应的程序。或者直接使用 go run asd.go 运行程序。

让我们来看下以上程序的各个部分:

(1) 第 1 行代码 package main 定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。

(2) 第 3 行 import "fmt" 告诉 Go 编译器这个程序需要使用 fmt 包,fmt 包实现了格式化 IO 的函数。

(3) 第 5 行 func main() 是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。

(4) 第 7 行 fmt.Println(...) 可以将字符串输出到控制台,并在最后自动增加换行字符 \n。使用 fmt.Print("hello, world\n") 可以得到相同的结果。Print 和 Println 这两个函数也支持使用变量,如:fmt.Println(arr)。如果没有特别指定,它们会以默认的打印格式将变量输出到控制台。

当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected)。

在 Go 程序中,一行代表一个语句结束。每个语句不需要像 C 家族中的其它语言一样以分号 ; 结尾,因为这些工作都将由 Go 编译器自动完成。如果你打算将多个语句写在同一行,它们必须使用 ; 人为区分,但在实际开发中我们并不鼓励这种做法。

Go 代码的特征解读

(1) 源程序以 .go 为后缀。

(2) 源程序默认为 UTF-8 编码。

(3) 标识符区分大小写,大写字母开头的标识符可以被外部包的代码所使用。

(4) 语句结尾的分号可以省略。

(5) 函数 func 开头,函数体开头的 " { " 必须在函数头所在行尾部,不能单独起一行,否则会报错。

(6) 宇符串字面量使用 " "(双引号)括起来。

(7) 调用包里面的方法通过点 " . " 访问符,比如示例中的 fmt.Println。

(8) main 函数所在的包名必须是 main。

Go 词法单元

在介绍 Go 语言具体语法之前,先介绍一下现代高级语言的源程序内部的几个概念: token、关键字、标识符、操作符、分隔符和字面量。

token

token 是构成源程序的基本不可再分割的单元。编译器编译源程序的第一步就是将源程序分割为一个个独立的 token,这个过程就是词法分析。Go 语言的 token 可以分为关键字、标识符、操作符、分隔符和字面常量等,分类如图所示。

Go 语言里面的 token 是怎么分割的?Go 的 token 分隔符有两类:一类是 操作符,还有一类自身没有特殊含义,仅用来分隔其他 token,被称为 纯分隔符

操作符:操作符就是一个天然的分隔符,同时其自身也是一个 token,语句如下所示: sum := a+b

其中," := " 和 " + " 既是分隔符,也是 token,所以这个简单的语句被分割为 5 个 token:" sum "" := ""  a "" + "" b ",Go 语言操作符的相关详细内容后面会涉及。

纯分隔符:其本身不具备任何语法含义,只作为其他 token 的分割功能。包括空格制表符换行符回车符,多个相邻的空格或者制表符会被编译器看作分隔符处理,例如:package main。

这是一个包声明的语句,package 和 main 之间可以有任意多个空格或者制表符,Go 编译器会将其作为一个分隔符处理,最后分离出来两个 token : package 和 main。

标识符

编程语言的标识符用来标识变量、类型、常量等语法对象的符号名称,其在语法分析时作为一个 token 存在。编程语言的标识符总体上分为两类:一类是语言设计者预留的标识符, 一类是编程者可以自定义的标识符。前者一般由语言设计者确定,包括语言的预声明标识符及用于后续语言扩展的保留字;后者是用户在编程过程中自行定义的变量名、常量名、函数名等一切符合语言规范的标识符。有一点需要注意,用户自定义的标识符不应该使用语言设计者的预留标识符,这可能导致歧义,并严重影响代码的可读性。

Go 的标识符构成规则是:开头第一个字符必须是字母或下划线,后面跟任意多个字符、数字或下划线,并且区分大小写,Unicode 字符也可以作为标识符的构成,但是一般不推荐这么使用。我们在定义新的标识符时要避开 Go 语言预声明标识符,以免引起混乱。

Go 语言预声明的标识符包括关键字、内置数据类型标识符、常量值标识符、内置函数和空白标识符。在写 Go 源程序的过程中,用户自定义标识符用在包名、函数名、自定义类型名、变量名和常量名等上。

关键字

编程语言里面的关键字是指语言设计者保留的有特定语法含义的标识符,这些关键字有自己独特的用途和语法含义,它们一般用来控制程序结构,每个关键字都代表不同语义的语法糖。

语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。举个例子:在 C 语言里用 a[i] 表示 *(a+i),由此可见语法糖不是 "现代语言" 独有,这种写法简洁明了,容易被人理解。

Go 语言是一门极简的语言,只有如下 25 个关键字 :

这 25 个关键字按照功能又可以分为三个部分:

引导程序整体结构的 8 个关键字

package    定义包名的关键字
import     导入包名关键字
const      常量声明关键字
var        变量声明关键字
func       函数定义关键字
defer      延迟执行关键字
go         并发语法糖关键字
return     函数返回关键字

声明复合数据结构的 4 个关键字

struct     定义结构类型关键字
interface  定义接口类型关键字
map        声明或创建map类型关键字
chan       声明或创建通道类型关键字

控制程序结构的 13 个关键字

if else                                        if else语句关键字
for range break continue                       for 循环使用的关键字
switch select type case default fallthrough    switch 和 select 语句使用的关键字
goto                                           goto 跳转语句

内置数据类型标识符

丰富的内置类型支持是高级语言的基本特性,基本类型也是构造用户自定义类型的基础。为了标识每种内置数据类型,Go 定义了一套预声明标识符,这些标识符用在变量或常量声明时。Go 语言内置了 20 个预声明数据类型标识符。Go 语言按类别有以下几种数据类型:

布尔型:bool
整型:byte、int、int8、int16、int32、int64、uint、unint8、uint16、uint32、uint64、uintprt
浮点型:float32、float64
复数:complex64、complex128
错误类型:error
字符和字符串型:rune、string

Go 是一种强类型静态编译型语言,在定义变量和常量时需要显式地指出数据类型,当然 Go 也支持自动类型推导,在声明初始化内置类型变量时,Go 可以自动地进行类型推导。但是在定义新类型或函数时,必须显式地带上类型标识符。

内置函数

make、new、len、cap、append、copy、delete、panic、recover、close、complex、real、image、print、println。内置函数也是高级语言的一种语法糖,由于其是语言内置的,不需要用 import 引入,内置函数具有全局可见性。注意到其中有以小写字母开头的,但是并不影响其全局可用性。

常量值标识符

true false    true 和 false 表示 bool 类型的两常量值:真和假
iota          用在连续的枚举类型的声明中
nil           指针/引用型的变量的默认值就是 nil 

空白标识符

_ ,空白标识符有特殊的含义,用来声明-个匿名的变量,该变量在赋值表达式的左端,空白标识符引用通常被用作占位,比如忽略函数多个返回值中的一个和强制编译器做类型检查。

Go 的源程序基本构成:

(1) 关键字引导程序的基本结构。

(2) 内置类型标识符辅助声明变量和常量。

(3) 字面量辅助变量和常量的初始化。

(4) 分隔符帮助 Go 编译器识别各个 token。

(5) 操作符、变量和关键字一起构成丰富的语法单元。

变量和常量

变量

变量使用一个名称来绑定一块内存地址,该内存地址中存放的数据类型由定义变量时指定的类型决定,该内存地址里面存放的内容可以改变。Go 语言变量声明:

(1) 显式的完整声明

var varName dataType  = value
例如:
var a int = 1
var a int = 2*3
var a int = b

关键字 var 用于变量声明,varName 是变量名标识符,dataType 是基本类型。value 是变量的初始值,初始值可以是字面量,也可以是其他变量名,还可以是一个表达式;如果不指定初始值,则 Go 默认将该变量初始化为类型的零值。Go 的变量声明后就会立即为其分配空间。

(2) 根据值自动判断变量类型

var v_name = valuepackage main
import "fmt"
func main() {var d = truefmt.Println(d)
}

(3) 短类型声明

varName := valuepackage main
import "fmt"
func main() {f := "Runoob"    // var f string = "Runoob"fmt.Println(f)
}

注意:" := " 声明只能出现在函数内,而不可以用于全局变量的声明与赋值:= 左侧如果没有声明新的变量,就产生编译错误。

var intVal int
intVal :=1            // 这时候会产生编译错误
intVal,intVal1 := 1,2 // 此时不会产生编译错误,因为有声明新的变量,因为 := 是一个声明语句package main
import "fmt"
func main() {f := "Runoob" // var f string = "Runoob"fmt.Println(f)
}

多变量声明

类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3var vname1, vname2, vname3 = v1, v2, v3  和 python 很像,不需要显示声明类型,自动推断
vname1, vname2, vname3 := v1, v2, v3     出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误这种因式分解关键字的写法一般用于声明全局变量
var (vname1 v_type1vname2 v_type2
)多变量可以在同一行进行赋值,如:
var a, b, c int
var c string
a, b, c = 5, 7, "abc"
package mainvar x, y int
var (  // 这种因式分解关键字的写法一般用于声明全局变量a intb bool
)var c, d int = 1, 2
var e, f = 123, "hello"//这种不带声明格式的只能在函数体中出现
//g, h := 123, "hello"func main(){g, h := 123, "hello"println(x, y, a, b, c, d, e, f, g, h)
}

如果函数体内的一个值声明了但是没有使用,会报错(declared and not used)。只有个使用了那个值后,错误才会移除。但是全局变量是允许声明但不使用的。例如:下面的代码只会报 a declared and not used 的错,而 testasd 不会影响程序。

package mainimport "fmt"
var testasd intfunc main() {var a string = "abc"fmt.Println("hello, world")
}

空白标识符在函数返回值时的使用:

package mainimport "fmt"func main() {_, numb, strs := numbers() //只获取函数返回值的后两个fmt.Println(numb,strs)
}//一个可以返回多个值的函数
func numbers()(int,int,string){a , b , c := 1 , 2 , "str"return a,b,c
}

常量

常量使用一个名称来绑定一块内存地址,该内存地址中存放的数据类型由定义常量时指定的类型决定,而且该内存地址里面存放的内容不可以改变。Go 中常量分为布尔型、宇符串型和数值型常量。常量存储在程序的只读段里(.rodata section)。

常量的定义格式:const identifier [type] = value,你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。

显式类型定义: const b string = "abc"
隐式类型定义: const b = "abc"
package mainimport "fmt"func main() {const LENGTH int = 10const WIDTH int = 5  var area intconst a, b, c = 1, false, "str" //多重赋值area = LENGTH * WIDTHfmt.Printf("面积为 : %d", area)println()println(a, b, c)
}

常量作枚举

const (Unknown = 0Female = 1Male = 2
)

常量可以用 len(),cap(),unsafe.Sizeof() 函数计算表达式的值。常量表达式中,函数必须是内置函数,否则编译不过:

package mainimport "unsafe"
const (a = "abc"b = len(a)c = unsafe.Sizeof(a)
)func main(){println(a, b, c)
}

iota

iota,特殊常量,可以认为是一个可以被编译器修改的常量。iota 在 const 关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

package mainimport "fmt"func main() {const (a = iota   //0b          //1c          //2d = "ha"   //独立值,iota += 1e          //"ha"   iota += 1f = 100    //iota +=1g          //100  iota +=1h = iota   //7,恢复计数i          //8)fmt.Println(a,b,c,d,e,f,g,h,i)
}输出:0 1 2 ha ha 100 100 7 8

变量的作用域

作用域为已声明标识符所表示的常量、类型、变量、函数或包在源代码中的作用范围。Go 语言中变量可以在三个地方声明:函数内定义的变量称为局部变量、函数外定义的变量称为全局变量、函数定义中的变量称为形式参数。

package mainimport "fmt"/* 声明全局变量 */
var a int = 20;func main() {/* main 函数中声明局部变量 */var a int = 10var b int = 20var c int = 0fmt.Printf("main()函数中 a = %d\n",  a);c = sum( a, b);fmt.Printf("main()函数中 c = %d\n",  c);
}/* 函数定义-两数相加 */
func sum(a, b int) int {fmt.Printf("sum() 函数中 a = %d\n",  a);fmt.Printf("sum() 函数中 b = %d\n",  b);return a + b;
}

字符串

Go 语言将字符串作为一种原生的基本数据类型, 字符串的初始化可以使用字符串字面量。例如 :var a = "hello, world" 。

(1) 字符串是常量,可以通过类似数组的索引访问其字节单元,但是不能修改某个字节的值。例如 :

var a = "hello, world"
b : = a [0]
a[1] = 'a '             //error

(2) 字符串转换为切片 [ ]byte(s) 要慎用,尤其是当数据量较大时(每转换一次都需复制内容)。例如:

a := "hello, world!"
b : = []byte (a)

(3) 字符串尾部不包含 NULL 字符,这一点和 C/C++ 不一样。

(4) 字符串类型底层实现是一个二元的数据结构,一个是指针指向字节数组的起点,另一个是长度。 例如 :

type stringStruct struct {str unsafe.Pointer   //指向底层字节数组的指针len int              //字节数组长度
}

(5) 基于字符串创建的切片和原字符串指向相同的底层字符数组,一样不能修改,对字符串的切片操作返回的子串仍然是 string,而非 slice。例如:

a := "hello , world!"
b := a[0:4]
c := a[1: ]
d  = a[ :4]

(6) 字符串和切片的转换:字符串可以转换为字节数组,也可以转换为 Unicode 的字数组。例如:

a := "hello,世界!"
b := [ ]byte(a)
c := [ ]rune(a)

(7) 字符串的运算。例如:

a := "hello"
b := "world"
c := a + b     // 字符串的拼接
len(a)         // 内置的 len 函数获取字符串长度d := "hello,世界!"
for i := 0; i < len(d); i++ {fmt . Println(d[i])
}for i, v := range d {         fmt . Println(i , v)
}

rune 类型

Go 内置两种字符类型:一种是 byte 的字节类类型( byte 是 uint 的别名),另一种是表示 Unicode 编码的字符 rune。rune 在 Go 内部是 int32 类型的别名,占用 4 个字节。Go 语言默认的字符编码就是 UTF-8 类型的,如果需要特殊的编码转换,则使用 Unicode/UTF-8 标准包。

package mainimport "fmt"func main() {asd := "123"var str []rune = []rune(asd)print(len(str))fmt.Printf("%c" , str[0])
}

复合数据类型

顾名思义,复合数据类型就是由其他类型组合而成的类型。Go 语言基本的复合数据类型有指针、数组、切片、字典(map)、通道、结构和接口,它们的字面量格式如下:

* pointerType             指针类型使用*后面跟其指向的类型名
[n] elementType           数组类型使用[n]后面跟数纽元素类型来表示,n 表示该数组的长度
[] elementType            切片类型使用[]后面跟切片元素类型来表示
map [keyType]valueType    map 类型使用 map[键类型]值类型来表示chan valueType            通道使用 chan 后面跟通道元素类型来表示struct {                  结构类型使用 struct{} 将各个结构字段扩起来表示feildType feildTypefeildType feildType...
}interface {               接口类型使用 interface{}将各个方法括起来表示method1(inputParams) (returnParams)method2(inputParams) (returnParams)...
}

指针

Go 语言支持指针,指针的声明类型为 *T,Go 同样支持多级指针 **T。通过在变量名前加 & 来获取变量的地址。指针的特点如下:

(1) 在赋值语句中,*T 出现在 "=" 左边表示指针声明,*T 出现在 "=" 右边表示取指针指向的值( varName 为变量名)。示例如下:

var a = 11
p := &a     *p 和 a 的位都是 11

(2) 结构体指针访问结构体字段仍然使用 "." 点操作符,Go 语言没有 "->" 操作符。例如:

type User struct{name stringage int
}andes := User{name:"andes",age:18,
}p := &andes
fmt.Println(p.name)     p.name 通过 "." 操作符访问成员变量

(3) Go 不支持指针的运算。

Go 由于支持垃圾回收,如果支持指针运算,则会给垃圾回收的实现带来很多不便,在 C 和 C++ 里面指针运算很容易出现问题,因此 Go 直接在语言层面禁止指针运算。例如:

a := 1234
p := &a      Go语言里面自增、自减操作符是语句而不是表达式
p++          不允许,报 non-numeric type *int 错误

(4) 函数中允许返回局部变量的地址,Go 编译器使用"栈逃逸" 机制将这种局部变量的空间分配在堆上。例如:

func sum(a, b int) *int {sum := a + breturn &sum          允许,sum会分配在 heap 上
}
package mainimport "fmt"func main() {var a int= 20      /* 声明实际变量 */var ip *int        /* 声明指针变量 */ip = &a            /* 指针变量的存储地址 */fmt.Printf("a 变量的地址是: %x\n", &a  )/* 指针变量的存储地址 */fmt.Printf("ip 变量储存的指针地址: %x\n", ip )/* 使用指针访问值 */fmt.Printf("*ip 变量的值: %d\n", *ip )
}空指针判断:
if(ptr != nil)    // ptr 不是空指针
if(ptr == nil)    // ptr 是空指针 
package mainimport "fmt"func main() {/* 定义局部变量 */var a int = 100var b int= 200fmt.Printf("交换前 a 的值 : %d\n", a )fmt.Printf("交换前 b 的值 : %d\n", b )/* 调用函数用于交换值* &a 指向 a 变量的地址* &b 指向 b 变量的地址*/swap(&a, &b);fmt.Printf("交换后 a 的值 : %d\n", a )fmt.Printf("交换后 b 的值 : %d\n", b )
}func swap(x *int, y *int) {var temp inttemp = *x    /* 保存 x 地址的值 */*x = *y      /* 将 y 赋值给 x */*y = temp    /* 将 temp 赋值给 y */
}
package main
import "fmt"func main() {var a intvar ptr *intvar pptr **inta = 3000/* 指针 ptr 地址 */ptr = &a/* 指向指针 ptr 地址 */pptr = &ptr/* 获取 pptr 的值 */fmt.Printf("变量 a = %d\n", a )fmt.Printf("指针变量 *ptr = %d\n", *ptr )fmt.Printf("指向指针的指针变量 **pptr = %d\n", **pptr)
}

数组

数组的类型名是 [n]elemetType,其中 n 是数组长度,elementType 是数组元素类型。 比如一个包含 2 个 int 类型元素的数组类型可表示为 [2]int。数组一般在创建时通过字面量初始化,单独声明一个数组类型变量而不进行初始化是没有意义的。

数组初始化

a := [3]int{ 1, 2, 3}       指定长度和初始化字面量
a := [ ... ]int{ 1, 2, 3}   不指定长度,但是由后面的初始化列表数量来确定其长度
a := [3]int{ 1:1, 2:3}      指定总长度,并通过索引值进行初始化,没有初始化元素时使用类型默认值
a := [ ... ]int{ 1:1, 2:3}  不指定总长度,通过索引值进行初始化,数组长度由最后一个索引值确定,没有指定索引的元素被初始化为类型的零值

数组的特点

(1) 数组创建完长度就固定了,不可以再追加元素。

(2) 数组是值类型的,数组赋值或作为函数参数都是值拷贝

(3) 数组长度是数组类型的组成部分,[10]int 和 [20]int 表示不同的类型。

(4) 可以根据数组创建切片。

package mainimport "fmt"func main() {var n [10]int /* n 是一个长度为 10 的数组 */var i,j int/* 为数组 n 初始化元素 */        for i = 0; i < 10; i++ {n[i] = i + 100 /* 设置元素为 i + 100 */}/* 输出每个数组元素的值 */for j = 0; j < 10; j++ {fmt.Printf("Element[%d] = %d\n", j, n[j] )}
}
package mainimport "fmt"func main() {/* 数组长度为 5 */var  balance = [5]int {1000, 2, 3, 17, 50}var avg float32/* 数组作为参数传递给函数 */avg = getAverage( balance, 5 ) ;/* 输出返回的平均值 */fmt.Printf( "平均值为: %f ", avg );
}
func getAverage(arr [5]int, size int) float32 {var i,sum intvar avg float32  for i = 0; i < size;i++ {sum += arr[i]}avg = float32(sum) / float32(size)return avg;
}

切片(Slice)

Go 语言切片是对数组的抽象。Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

type slice struct {array unsafe.Pointerlen intcap int
)

Go 为切片维护三个元素——指向底层数组的指针切片的元素数量底层数组的容量。具体结构如图所示:

定义切片

声明一个未指定大小的数组来定义切片(切片不需要说明长度):

var identifier []type

或使用 make() 函数来创建切片:

var slice1 []type = make([]type, len)
也可以简写为
slice1 := make([]type, len)也可以指定容量,其中capacity为可选参数。
make([]T, length, capacity)
这里 len 是数组的长度并且也是切片的初始长度。

切片初始化

s :=[] int {1,2,3 }
直接初始化切片,[]表示是切片类型,{1,2,3}初始化值依次是1,2,3。其cap = len = 3s := arr[:]
初始化切片s,是数组arr的引用s := arr[startIndex:endIndex]
将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片s := arr[startIndex:]
默认 startIndex 时将表示从 arr 的第一个元素开始s1 := s[startIndex:endIndex]
通过切片 s 初始化切片 s1s := make([]int,len,cap)
通过内置函数 make() 初始化切片s,[]int 标识为其元素类型为 int 的切片

len() 和 cap() 函数

切片是可索引的,并且可以由 len() 方法获取长度。切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。以下为具体实例:

package mainimport "fmt"func main() {var numbers = make([]int,3,5)printSlice(numbers)
}func printSlice(x []int){fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

空(nil)切片

一个切片在未初始化之前默认为 nil,长度为 0,实例如下:

package mainimport "fmt"func main() {var numbers []intprintSlice(numbers)if(numbers == nil){fmt.Printf("切片是空的")}
}func printSlice(x []int){fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

切片截取

可以通过设置下限及上限来设置截取切片 [lower-bound:upper-bound],实例如下:

package mainimport "fmt"func main() {/* 创建切片 */numbers := []int{0,1,2,3,4,5,6,7,8}  printSlice(numbers)/* 打印原始切片 */fmt.Println("numbers ==", numbers)/* 打印子切片从索引1(包含) 到索引4(不包含)*/fmt.Println("numbers[1:4] ==", numbers[1:4])/* 默认下限为 0*/fmt.Println("numbers[:3] ==", numbers[:3])/* 默认上限为 len(s)*/fmt.Println("numbers[4:] ==", numbers[4:])numbers1 := make([]int,0,5)printSlice(numbers1)/* 打印子切片从索引  0(包含) 到索引 2(不包含) */number2 := numbers[:2]printSlice(number2)/* 打印子切片从索引 2(包含) 到索引 5(不包含) */number3 := numbers[2:5]printSlice(number3)}func printSlice(x []int){fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

append() 和 copy() 函数

如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。下面的代码描述了从拷贝切片的 copy 方法和向切片追加新元素的 append 方法。

package mainimport "fmt"func main() {var numbers []intprintSlice(numbers)/* 允许追加空切片 */numbers = append(numbers, 0)printSlice(numbers)/* 向切片添加一个元素 */numbers = append(numbers, 1)printSlice(numbers)/* 同时添加多个元素 */numbers = append(numbers, 2,3,4)printSlice(numbers)/* 创建切片 numbers1 是之前切片的两倍容量*/numbers1 := make([]int, len(numbers), (cap(numbers))*2)/* 拷贝 numbers 的内容到 numbers1 */copy(numbers1,numbers)printSlice(numbers1)
}func printSlice(x []int){fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

map

Go 语言内置的字典类型叫 map。map 的类型格式是:map[K]T,其中 K 可以是任意可以进行比较的类型,T 是值类型。map 也是一种引用类型。

(1) map 的创建

使用字面量创建。例如:

/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_typema := map[string]int{ "a": 1, "b": 2)
fmt.Println(ma["a"] )
fmt.Println(ma["b"])

使用内置的 make 函数创建。例如:

/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)make(map[K]T )        //map 的容量使用默认位
make(map[K]T, len)    //map 的容量使用给定的 len 值
mp1 := make(map[int]string)
mp2 := make(map[int]string , 10)
mp1[1] = "tom"
mp2[1] = "pony"
fmt.Println(mp1[1]) //tom
fmt.Println(mp2[1]) //pony

(2) map 支持的操作

① map 的单个键值访问格式为 mapName[key],更新某个 key 的值时 mapName[key] 放到等号左边,访问某个 key 的值时 mapName[key] 放在等号的右边。

② 可以使用 range 遍历一个 map 类型变量,但是不保证每次选代元素的顺序。

删除 map 中的某个键值,使用如下语法:delete(mapName,key)。delete 是内置函数,用来删除 map 中的某个键值对。

④ 可以使用内置的 len() 函数返回 map 中的键值对数量。例如:

mp := make(map[int]string)
mp[1] = "tom"
mp[1] = "pony"
mp[2] = "jaky"
mp[3] = "andes "
delete (mp , 3)fmt.Println (mp [1])
fmt.Println(len(mp))       // len函数返回 map 中的键值对的数量
for k, v := range mp {     // range 支持边历 mp,但不保证每次遍历次序是一样的fmt.Println(" key=", k,"value=", v)
}

注意:

(1) Go 内置的 map 不是并发安全的,并发安全的 map 可以使用标准包 sync 中的 map。

(2) 不要直接修改 map value 内某个元素的值,如果想修改 map 的某个键值,则必须整体赋值。例如:

type User struct {name stringage int
}ma := make(map[int]User)andes := User{name : "andes",age: 18 ,
}ma[1] = andes
// ma [1].age = 19         // ERROR,不能通过 map 引用直接修改andes.age = 19
ma[1] = andes              // 必须整体替换 value
fmt.Printf (" %v\n ", ma)
package mainimport "fmt"func main() {var countryCapitalMap map[string]string /*创建集合 */countryCapitalMap = make(map[string]string)/* map插入key - value对,各个国家对应的首都 */countryCapitalMap [ "France" ] = "巴黎"countryCapitalMap [ "Italy" ] = "罗马"countryCapitalMap [ "Japan" ] = "东京"countryCapitalMap [ "India " ] = "新德里"/*使用键输出map值 */for country := range countryCapitalMap {fmt.Println(country, "首都是", countryCapitalMap [country])}/*查看元素在集合中是否存在 */capital, ok := countryCapitalMap [ "American" ] /*如果确定是真实的,则存在,否则不存在 *//*fmt.Println(capital) *//*fmt.Println(ok) */if (ok) {fmt.Println("American 的首都是", capital)} else {fmt.Println("American 的首都不存在")}
}输出:
France 首都是 巴黎
Italy 首都是 罗马
Japan 首都是 东京
India  首都是 新德里
American 的首都不存在
package mainimport "fmt"func main() {/* 创建map */countryCapitalMap := map[string]string{"France": "Paris", "Italy": "Rome", "Japan": "Tokyo", "India": "New delhi"}fmt.Println("原始map")/* 打印map */for country := range countryCapitalMap {fmt.Println(country, "首都是", countryCapitalMap [ country ])}/*删除元素*/ delete(countryCapitalMap, "France")fmt.Println("法国条目被删除")fmt.Println("删除元素后map")/*打印map*/for country := range countryCapitalMap {fmt.Println(country, "首都是", countryCapitalMap [ country ])}
}输出:
原始map
India 首都是 New delhi
France 首都是 Paris
Italy 首都是 Rome
Japan 首都是 Tokyo
法国条目被删除
删除元素后map
Italy 首都是 Rome
Japan 首都是 Tokyo
India 首都是 New delhi

范围(Range)

Go 语言中 range 关键字用于 for 循环中迭代数组、切片、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。

package main
import "fmt"
func main() {//这是我们使用range去求一个slice的和。使用数组跟这个很类似nums := []int{2, 3, 4}sum := 0for _, num := range nums {sum += num}fmt.Println("sum:", sum)//在数组上使用range将传入index和值两个变量。上面那个例子我们不需要使用该元素的序号,所以我们使用空白符"_"省略了。有时侯我们确实需要知道它的索引。for i, num := range nums {if num == 3 {fmt.Println("index:", i)}}//range也可以用在map的键值对上。kvs := map[string]string{"a": "apple", "b": "banana"}for k, v := range kvs {fmt.Printf("%s -> %s\n", k, v)}//range也可以用来枚举Unicode字符串。第一个参数是字符的索引,第二个是字符(Unicode的值)本身。for i, c := range "go" {fmt.Println(i, c)}
}输出:
sum: 9
index: 1
a -> apple
b -> banana
0 103
1 111

struct

Go 中的 struct 类型和 C 类似,由多个不同类型元素组合而成。这里面有两层含义:第一 ,struct 结构中的类型可以是任意类型;第二, struct 的存储空间是连续的,其字段按照声明时的顺序存放(注意字段之间有对齐要求)。struct 有两种形式:一种是 struct 类型字面量,另一种是使用 type 声明的自定义 struct 类型。

(1) struct 类型字面量的声明格式:

struct {FeildName FeildTypeFeildName FeildTypeFeildName FeildType
}

(2) 自定义 struct 类型声明格式:

type TypeName struct {FeildName FeildTypeFeildName FeildTypeFeildName FeildType
}

实际使用 struct 字面量的场景不多,更多的时候是通过 type 自定义一个新的类型来实现的。type 是自定义类型的关键字,不但支持 struct 类型的创建,还支持任意其他子定义类型的创建。

(3) struct 类型变量的初始化。示例如下:

type Person struct {Name stringAge int
}type Student struct {*PersonNumber int
}a := Person{"Tom", 21)
按照类型声明顺序,逐个赋值,一旦 struct 增加字段,则 整个初始化语句会报错推荐下面这种使用 Feild 名字的初始化方式,没有指定的字段则默认初始化为类型的零值
p := &Person{Name:"tata",Age: 12 ,
}s := Student{Person: p,Number: 110,
}
package mainimport "fmt"type Books struct {title stringauthor stringsubject stringbook_id int
}func main() {var Book1 Books        /* Declare Book1 of type Book */var Book2 Books        /* Declare Book2 of type Book *//* book 1 描述 */Book1.title = "Go 语言"Book1.author = "www.runoob.com"Book1.subject = "Go 语言教程"Book1.book_id = 6495407/* book 2 描述 */Book2.title = "Python 教程"Book2.author = "www.runoob.com"Book2.subject = "Python 语言教程"Book2.book_id = 6495700/* 打印 Book1 信息 */printBook(&Book1)/* 打印 Book2 信息 */printBook(&Book2)
}
func printBook( book *Books ) {fmt.Printf( "Book title : %s\n", book.title)fmt.Printf( "Book author : %s\n", book.author)fmt.Printf( "Book subject : %s\n", book.subject)fmt.Printf( "Book book_id : %d\n", book.book_id)
}

控制结构

现代计算机存储结构无论 "普林斯顿结构",还是 "哈佛结构",程序指令都是线性地存放在存储器上。程序执行从本质上来说就是两种模式:顺序和跳转

顺序就是按照程序指令在存储器上的存放顺序逐条执行。
跳转就是遇到跳转指令就跳转到某处继续线性执行。

Go 是一门高级语言,其源程序虽然经过了高度的抽象并封装了很多语法糖,但还是跳不出这个模式(这里暂时不考虑 goroutine 引入并发后的执行视图变化)。

顺序在 Go 里面体现在从 main 函数开始逐条向下执行,就像我们的程序源代码顺序一样;跳转在 Go 里面体现为多个语法糖,包括 goto 语句和函数调用、分支( if、switch 、select )、循环( for )等。跳转分为两种:一种是无条件跳转,比如函数调用和 goto 语句;一种是有条件的跳转,比如分支和循环。

备注:

Go 的源代码的顺序并不一定是编译后最终可执行程序的指令顺序,这里面涉及语言的运行时和包的加载过程。上面论述的主要目的是使读者从宏观上整体理解程序的执行过程,建立一个从源代码到执行体的大体映射概念,这个概念不那么精准,但对我们理解源程序到目标程序的构建非常有帮助。

if 语句特点

(1) if 后面的条件判断子句不需要用小括号括起来。

(2) " { " 必须放在行尾,和 if 或 if else 放在一行。

(3) if 后面可以带一个简单的初始化语句,并以分号分割,该简单语句声明的变量的作用域是整个 if 语句块,包括后面的 else if 和 else 分支。

(4) Go 语言没有条件运算符 (a > b ? a:b),这也符合 Go 的设计哲学,只提供一种方法做事情。

(5) if 分支语句遇到 return 后直接返回,遇到 break 则跳过 break 下方的 if 语句块。

if x <= y {return y
} else {return x
}if x :=f(); x<y {  // 初始化语句中的声明变量 xreturn x
} else if x > z {  // x 在 else if 里面一样可以被访问return z
} else {return y
}
package main
import "fmt"func main() {/* 局部变量定义 */var a int = 100;/* 判断布尔表达式 */if a < 20 {/* 如果条件为 true 则执行以下语句 */fmt.Printf("a 小于 20\n" );} else {/* 如果条件为 false 则执行以下语句 */fmt.Printf("a 不小于 20\n" );}fmt.Printf("a 的值为 : %d\n", a);
}

switch 语句

switch 语句会根据传入的参数检测并执行符合条件的分支 。switch 的语法特点如下:

(1) switch 和 if 语句一样,switch 后面可以带一个可边的简单的初始化语句。

(2) switch 后面的表达式也是可选的,如果没有表达式,则 case 子句是一个布尔表达式,而不是一个值,此时就相当于多重 if else 语句。

(3) switch 条件表达式的值不像 C 语言那样必须限制为整数,可以是任意支持相等比较运算的类型变量。

(4) 通过 fallthough 语句来强制执行下一个 case 子句(不再判断下一个 case 子句的条件是否满足)。

(5) switch 支持 default 语句,当所有的 case 分支都不符合时,执行 default 语句, 并且 default 语句可以放到任意位置,并不影响 switch 的判断逻辑。

(6) switch 和 .(type) 结合可以进行类型的查询。

switch i := " Y "; i{           //switch 后面可以带上一个初始化语句case "y"," Y":              //多个 case 值使用逗号分隔fmt.Println ("yes" )    //yesfallthrough             //fallthrough 会跳过接下来的 case 条件表达式//直接执行下一个 case 语句case "n","N":fmt . Println(" no" )   // no
}switch {
case score >= 90:grade = 'A'
case score >= 80 :grade ='B'
case score >= 70 :grade ='C'
case score >= 60 :grade = 'D'
default :grade ='F'
}
fmt.Printf(" grade = %c\n ", grade)    //grade=B
package mainimport "fmt"func main() {/* 定义局部变量 */var grade string = "B"var marks int = 90switch marks {case 90: grade = "A"case 80: grade = "B"case 50,60,70 : grade = "C"default: grade = "D"  }switch {case grade == "A" :fmt.Printf("优秀!\n" )    case grade == "B", grade == "C" :fmt.Printf("良好\n" )      case grade == "D" :fmt.Printf("及格\n" )      case grade == "F":fmt.Printf("不及格\n" )default:fmt.Printf("差\n" );}fmt.Printf("你的等级是 %s\n", grade );
}
package mainimport "fmt"func main() {switch {case false:fmt.Println("1、case 条件语句为 false")fallthroughcase true:fmt.Println("2、case 条件语句为 true")fallthroughcase false:fmt.Println("3、case 条件语句为 false")fallthroughcase true:fmt.Println("4、case 条件语句为 true")case false:fmt.Println("5、case 条件语句为 false")fallthroughdefault:fmt.Println("6、默认 case")}
}输出:
2、case 条件语句为 true
3、case 条件语句为 false
4、case 条件语句为 true

for 语句

Go 语言仅支持一种循环语句,即 for 语句,同样遵循 Go 的设计哲学,只提供一种方法做事情,把事情做好。Go 对应 C 循环的三种场景如下:

(1) 类似 C 里面的 for 循环语句

for init; condition; post { }
init:一般为赋值表达式,给控制变量赋初值;
condition:关系表达式或逻辑表达式,循环控制条件;
post:一般为赋值表达式,给控制变量增量或减量。
package mainimport "fmt"func main() {sum := 0for i := 0; i <= 10; i++ {sum += i}fmt.Println(sum)
}

(2) 类似 C 里面的 while 循环语句

for condition { }
package mainimport "fmt"func main() {sum := 1for ; sum <= 10; {sum += sum}fmt.Println(sum)// 这样写也可以,更像 While 语句形式for sum <= 10{sum += sum}fmt.Println(sum)
}

(3) 类似 C 里面的 while (1) 死循环语句

for { }
package mainimport "fmt"func main() {sum := 0for {sum++        // 无限循环下去}fmt.Println(sum) // 无法输出
}

for 还有一种用法,是对数组、切片、字符串、map 和通道的访问,语法格式如下:

访问 map
for key, value := range map{}
for key := range map{}访问数组
for index, value := range arry{}
for index := range arry{}
for , value := range arry{}访问切片
for index, value := range slice{}
for index := range slice{}
for _, value := range slice{}访问通道
for value := range channel {}
package main
import "fmt"func main() {strings := []string{"google", "runoob"}for i, s := range strings {fmt.Println(i, s)}numbers := [6]int{1, 2, 3, 5}for i,x:= range numbers {fmt.Printf("第 %d 位 x 的值 = %d\n", i,x)}
}输出:
0 google
1 runoob
第 0 位 x 的值 = 1
第 1 位 x 的值 = 2
第 2 位 x 的值 = 3
第 3 位 x 的值 = 5
第 4 位 x 的值 = 0
第 5 位 x 的值 = 0

标签和跳转

Go 语言使用标签 ( Lable )来标识一个语句的位置,用于 goto、break、continue 语句的跳转,标签的语法是:Lable:Statement。

goto

goto 语句用于函数的内部的跳转,需要配合标签一起使用,具体的格式如下: goto Lable

goto Lable 的语义是跳转到标签名后的语句处执行,goto 语句有以下几个特点:

(1) goto 语句只能在函数内跳转。

(2) goto 语句不能跳过内部变量声明语句,这些变量在 goto 语句的标签语句处又是可见的。例如:

    goto L  //BAD,跳过 v := 3 这条语句是不允许的v := 3
L:

(3) goto 语句只能跳到同级作用域或者上层作用域内,不能跳到内部作用域内。例如:

if n%2 == 1 {goto L1
}
for n > 0 {f()n--
L1:f()n--
}
package main import "fmt"func main() {//print9x()gotoTag()
}//嵌套for循环打印九九乘法表
func print9x() {for m := 1; m < 10; m++ {for n := 1; n <= m; n++ {fmt.Printf("%dx%d=%d ",n,m,m*n)}fmt.Println("")}
}//for循环配合goto打印九九乘法表
func gotoTag() {for m := 1; m < 10; m++ {n := 1LOOP: if n <= m {fmt.Printf("%dx%d=%d ",n,m,m*n)n++goto LOOP} else {fmt.Println("")}n++}
}

break

break 用于函数内跳出 for、switch、select 语句的执行,有两种使用格式:

(1) 单独使用,用于跳出 break 当前所在的 for、switch、select 语句的执行。

(2) 和标签一起使用,用于跳出标签所标识的 for、switch 、select 语句的执行。可用于跳出多重循环,但标签和 break 必须在同一个函数内。例如:

L1:for i := 0; ; i++{for j := 0; ; j ++ {if i >= 5 {// 跳出 L1 标签所在的 for 循环break L1}if j > 10 {// 默认仅跳出离 break 最近的内层循环break}}}
package mainimport "fmt"func main() {// 不使用标记fmt.Println("---- break ----")for i := 1; i <= 3; i++ {fmt.Printf("i: %d\n", i)for i2 := 11; i2 <= 13; i2++ {fmt.Printf("i2: %d\n", i2)break}}// 使用标记fmt.Println("---- break label ----")re:for i := 1; i <= 3; i++ {fmt.Printf("i: %d\n", i)for i2 := 11; i2 <= 13; i2++ {fmt.Printf("i2: %d\n", i2)break re}}
}输出:
---- break ----
i: 1
i2: 11
i: 2
i2: 11
i: 3
i2: 11
---- break label ----
i: 1
i2: 11    

continue

continue 用于跳出 for 循环的本次选代,跳到 for 循环的下一次选代的 post 语句处执行,也有两种使用格式:

(1) 单独使用,用于跳出 continue 当前所在的 for 循环的本次迭代。

(2) 和标签一起使用,用于跳出标签所标识的 for 语句的本次选代,但标签和 continue 必须在同一个函数内。例如:

L1:for i := 0; ; i++ {for j := 0 ; ; j ++ {if i >= 5 {// 跳到 Ll 标签所在的 for 循环 i++ 处执行continue L1// the following is not executed}if j > 10 {continue}}}    
package mainimport "fmt"func main() {// 不使用标记fmt.Println("---- continue ---- ")for i := 1; i <= 3; i++ {fmt.Printf("i: %d\n", i)for i2 := 11; i2 <= 13; i2++ {fmt.Printf("i2: %d\n", i2)continue}}// 使用标记fmt.Println("---- continue label ----")re:for i := 1; i <= 3; i++ {fmt.Printf("i: %d\n", i)for i2 := 11; i2 <= 13; i2++ {fmt.Printf("i2: %d\n", i2)continue re}}
}输出:
---- continue ----
i: 1
i2: 11
i2: 12
i2: 13
i: 2
i2: 11
i2: 12
i2: 13
i: 3
i2: 11
i2: 12
i2: 13
---- continue label ----
i: 1
i2: 11
i: 2
i2: 11
i: 3
i2: 11

Go 语言学习笔记(一):基础知识相关推荐

  1. HTML5学习笔记 —— JavaScript基础知识

    HTML5学习笔记 -- JavaScript基础知识 标签: html5javascriptweb前端 2017-05-11 21:51 883人阅读 评论(0) 收藏 举报 分类: JavaScr ...

  2. JS学习笔记——入门基础知识总结

    JS入门基础知识总结1 前言 基础背景知识 一.产生历史: 二.特点: 三.应用方向: 四.Javascript组成: JavaScript书写使用方式 一.行内式(了解即可,项目中不使用,日常练习尽 ...

  3. php基础教学笔记,php学习笔记:基础知识

    php学习笔记:基础知识 2.每行结尾不允许有多余的空格 3.确保文件的命名和调用大小写一致,是由于类Unix系统上面,对大小写是敏感的 4.方法名只允许由字母组成,下划线是不允许的,首字母要小写,其 ...

  4. 【学习笔记--FMCW基础知识】

    学习笔记--FMCW基础知识 前言 mmWave测距原理 mmWave区分多个物体 mmWave的距离分辨率(Range Solution) mmWave的最大测量距离 前言 由于工作原因需要了解TI ...

  5. SAS学习笔记1——基础知识(库、PDV、变量选择、观测值排序、创建新变量

    SAS学习笔记1--基础知识 1.逻辑库.临时库.永久库 2.数据步 2.1数据步语法 2.2 数据步的编译和执行过程 2.3变量的选择 2.3.1 keep和drop语句 2.4变量的重命名rena ...

  6. Go语言学习笔记—golang基础简介

    视频来源:B站<golang入门到项目实战 [2022最新Go语言教程,没有废话,纯干货!]> 文章为自己整理的学习笔记,侵权即删,谢谢支持! 文章目录 一.Go语言简述 二.Go语言特点 ...

  7. Go语言学习笔记—golang基础语法

    视频来源:B站<golang入门到项目实战 [2022最新Go语言教程,没有废话,纯干货!]> 文章为自己整理的学习笔记,侵权即删,谢谢支持! 文章目录 golang基础语法 一.gola ...

  8. WebGL学习笔记(基础知识篇)

    WEBGL基础知识介绍 1.场景(scene) 场景如其名,即显示3D空间内物体的容器,就好比一个箱子是一个3D场景. 2.坐标系: webgl使用笛卡尔坐标系(宽度.高度和深度),我们也可以指定使用 ...

  9. 《UNIX 环境高级编程》学习笔记——UNIX 基础知识

    UNIX环境高级编程--UNIX 基础知识 引言 UNIX 体系结构 登录 文件和目录 输入和输出 程序和进程 出错处理 用户标识 信号 时间值 系统调用和库函数 引言 所有操作系统都为它们所允许的程 ...

  10. 单片机学习笔记——微机基础知识

    微机基础知识 微处理器,微机和单片机概念 微处理器的组成 一.运算器 主要寄存器 主要寄存器(IR),指令译码器(ID) 程序计数器(PC) 地址寄存器(AR) 二.控制器 存储器和输入输出接口 一. ...

最新文章

  1. CSS中background-position属性
  2. Mogees将手势识别技术运用于新产品
  3. Revit Family API 添加参数与尺寸标注
  4. python输入一个列表的语句_python自学笔记使用if语句处理列表作业
  5. mac boot2docker certs not valid with 1.7
  6. PIP 安装 numpy
  7. Windows10系统下,彻底删除卸载MySQL
  8. Linux内存管理:分页
  9. 接受字符串参数,返回一个元组,并分别统计字符串中大小写的个数
  10. 使用Eclipse进行远程调试 外加 ant 直接打包到服务器
  11. date java format_java-DateFormat
  12. win10打开internet信息服务器,Win10打开internet信息服务的方法
  13. 导入oracle 904,江湖救急..ora-904怎么处理?
  14. linux shell题库,shell习题-30
  15. 问卷设计中 你经常使用计算机吗,计算机应用基础课程调查问卷
  16. 初级算法题->有效的数独--弄清哈希表的本质
  17. WkwebView调节字体大小获取高度
  18. CUDA_ERROR_LAUNCH_TIMEOUT
  19. web前端开发中需要掌握的技术:
  20. 获取 ProgramData 文件夹路径

热门文章

  1. Kafka设计解析(五): Kafka Consumer设计解析
  2. tinyxml2解析XML文件
  3. VC++调用UpdateLayeredWindow实现半透明窗体【转】
  4. __try,__except,__finally,__leave异常模型机
  5. 低学历程序员的红利来了,这个政策来的太惊喜!
  6. 音视频技术开发周刊 | 190
  7. Apple 低延迟HLS分析
  8. DeepFocus,基于AI实现更逼真的VR图像
  9. 仅需少量视频观看记录,就可以精准推断你的习惯
  10. 1024程序猿节:揭秘腾讯老中青三代程序猿工位