Golang基础语法总结
一,关键字 (关键字25个)
break default func interface select case defer go map struct chan else goto
package switch const fallthrough if range type continue for import return var
二,数据类型,数据结构
NUMBER,STRING, BOOLEAN, ARRAYS,SLICES,MAPS
1,内建类型
int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64 bool byte rune string error
2,内置常量
true false iota nil
3,数据结构
array,slice,map
4,内建函数
make len cap new append copy close delete
complex real imag panic recover
close 用于channel 通讯。使用它来关闭channel
delete 用于在map 中删除实例。
len 和cap 可用于不同的类型, len 用于返回字符串、slice 和数组的长度.
new 用于各种类型的内存分配。
make 用于内建类型(map、slice 和channel)的内存分配。
copy 用于复制slice
append 用于追加slice。
panic 和recover 用于异常处理机制
print 和println 是底层打印函数,可以在不引入fmt 包的情况下使用。
complex、real 和imag 全部用于处理复数
三,变量声明
类型别名 type 类型别名 类型原名
使用const关键字:const 常量名 常量类型 = 常量值
数组 声明格式:var 数组名 [元素个数]元素类型
var arr1 [5]int //具有5个int元素的数组,未进行初始化,元素默认为零值
var arr2 [5]int{1, 2, 3, 4, 5} //定义并直接初始化
切片 声明格式:var 切片名 []切片类型
var sic1 []int //声明含有多个未知元素的切片
var sic2 []int{} //声明空切片
结构体声明 type 结构体名 struct {}:右括号必须和type在同一行
枚举声明 使用iota+const关键字
const ( r = iota //0 s = iota //1 t = iota //2 ) const ( u = iota //0 v //1 w //2 ) const ( u1 = iota //0 v1, w1, x1 = iota, iota, iota //1, 1, 1 )
var name, sex, addr string = "andy", "man", "xian" name := "string" // can not global numbers := make([]int, 5, 10) var m map[string]int m := make(map[string]int) for key := range m { delete(m, key) } data := map[string]string{"1": "A", "2": "B", "3": "C"} for k, v := range data { data[v] = k fmt.Printf("res %v\n", data) } type Person struct { Name string Age int } func add(a int, b int) (c int) { c = a + b return }
四,语法
for 初始条件; 判断条件; 条件变换 {
//TODO
}
range关键字的使用 作用:迭代遍历切片/数组中的每一个元素,默认有两个返回值,第一个为元素下标,第二个为元素数据
switch语句
golang保留break关键字,在switch语句中如果不写则默认添加
break可用于for/switch/select
continue只可以用于for
case后面可以跟多个条件
Go语言改进了 switch 的语法设计,case 与 case 之间是独立的代码块,不需要通过 break 语句跳出当前 case 代码块以避免执行到下一行
switch 中的表达式是可选的,可以省略。如果省略表达式,则相当于 switch true,这种情况下会将每一个 case 的表达式的求值结果与 true 做比较,如果相等,则执行相应的代码
//首先声明变量 var num1 int = 1 switch num1 { case 1: //... case 2: //... case 3, 4, 5: //... ... default: //... } //直接使用初始化语句 switch num2 := 1; num1 { case 1: //... case 2: //... ... default: //... } //switch后面可以不添加参数 var num int = 0 switch { case num > 90: //... case num < 90: //... default: //... }
五,其他注意事项
for 初始条件; 判断条件; 条件变换 {
//TODO
}
make和new
Go有两个数据结构创建函数:new和make。
new返回一个指向已清零内存的指针,而make返回一个复杂的结构。
基本的区别是 new(T) 返回一个 *T ,返回的这个指针可以被隐式地消除引用(图中的黑色箭头) 。而 make(T, args) 返回一个普通的T.
注意:空切片和 nil 切片是不同的,空切片指向的地址不是nil,指向的是一个内存地址,但是它没有分配任何内存空间,即底层元素包含0个元素。
不管是使用 nil 切片还是空切片,对其调用内置函数 append,len 和 cap 的效果都是一样的。
只有make能做的操作:
a.创建一个chan
b.创建一个内存预分配的map
c.创建一个内存预分配的slice,并且slice的len可以不等于cap
make用于内建类型(map、slice 和channel)的内存分配。new用于各种类型的内存分配。
内建函数new本质上说跟其它语言中的同名函数功能一样:new(T)分配了零值填充的T类型的内存空间,并且返回其地址,即一个*T类型的值。用Go的术语说,它返回了一个指针,指向新分配的类型T的零值。有一点非常重要:new返回指针。
内建函数make(T, args)与new(T)有着不同的功能,make只能创建slice、map和channel,并且返回一个有初始值(非零)的T类型,而不是*T。
本质来讲,导致这三个类型有所不同的原因是指向数据结构的引用在使用前必须被初始化。例如,一个slice,是一个包含指向数据(内部array)的指针、长度和容量的三项描述符;在这些项目被初始化之前,slice为nil。对于slice、map和channel来说,make初始化了内部的数据结构,填充适当的值。make返回初始化后的(非零)值。
make 是 引用类型 初始化的方法。
Go任何类型在未初始化时都对应一个零值:布尔类型是false,整型是0,字符串是"",而指针,函数,interface,slice,channel和map的零值都是nil。
olang 不是完全的面向对象语言,但是支持很多面向对象的特性,例如有结构体,接口,方法等。
bool类型不能转换为整型,整型也不能转换为bool类型,这种不能转换的类型叫做不兼容类型。
官方的go编译器限制channel最多能容纳到65535个元素
列表列表与切片和 map 不同的是,列表并没有具体元素类型的限制,因此,列表的元素可以是任意类型,这既带来了便利,也引来一些问题,
例如给列表中放入了一个 interface{} 类型的值,取出值后,如果要将 interface{} 转换为其他类型将会发生宕机。
container/list
nameList := list.New()
var nameList list.List
l.PushBack("fist")
l.PushFront(67)
go只提供了一种循环方式,即for循环,在使用时可以像c那样使用,也可以通过for range方式遍历容器类型如数组、切片和映射
六,未来范型
c++ : 编译时生成每种类型的方法,缺点编译慢,需要后端消除无用代码
java: 类型擦除,所有类型都转换成Object,取值再做一次拆箱,缺点运行慢
移除原因
支持泛型的Go 1.18 Beta 1版本发布以来,围绕着constraints包的争议很多。
主要是以下因素,导致Russ Cox决定从Go标准库中移除constraints包。
constraints名字太长,代码写起来比较繁琐。大多数泛型的代码只用到了any和comparable这2个类型约束。
constaints包里只有constraints.Ordered使用比较广泛,其它很少用。所以完全可以把Ordered设计成和any以及comparable一样,
都作为Go的预声明标识符,不用单独弄一个constraints包。
在官方的最新proposal里有提到,在Golang中,并不是所有的类型都满足+号运算。
在go 泛型的版本中,泛型函数只能使用类型参数所能实例化出的任意类型都能支持的操作。
函数可以通过type关键字引入额外的类型参数(type parameters)列表:func F(type T)(p T) { ... } 。
这些类型参数可以像一般的参数一样在函数体中使用。
类型也可以拥有类型参数列表:type M(type T) []T。
每个类型参数可以拥有一个约束:func F(type T Constraint)(p T) { ... }。
使用interface来描述类型的约束。
被用作类型约束的interface可以拥有一个预声明类型列表,限制了实现此接口的类型的基础类型。
使用泛型函数或类型时需要传入类型实参。
一般情况下,类型推断允许用户在调用泛型函数时省略类型实参。
如果类型参数具有类型约束,则类型实参必须实现接口。
泛型函数只允许进行类型约束所规定的操作。
尽管最新的proposal冗长而详尽,但总结起来如下:
函数和类型可以具有类型参数,该类型参数使用可选约束(接口类型)定义,约束描述了这些参数所需的方法和允许的类型。
当使用类型参数调用函数时,类型推断通常会允许用户省略类型参数。
泛型函数只能使用约束允许的所有类型支持的操作
此设计完全向后兼容,但建议对func F(x(T))的含义进行更改。
func printSlice[T any](s []T) { for _, v := range s { fmt.Printf("%v ", v) } fmt.Print("\n") } func main() { printSlice[int]([]int{1, 2, 3, 4, 5}) printSlice[float64]([]float64{1.01, 2.02, 3.03, 4.04, 5.05}) printSlice([]string{"Hello", "World"}) printSlice[int64]([]int64{5, 4, 3, 2, 1}) } type Addable interface { type int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, float32, float64, complex64, complex128, string } func add[T Addable] (a, b T) T { return a + b } func main() { fmt.Println(add(1,2)) fmt.Println(add("hello","world")) } func mapFunc[T any, M any](a []T, f func(T) M) []M { n := make([]M, len(a), cap(a)) for i, e := range a { n[i] = f(e) } return n } func main() { vi := []int{1, 2, 3, 4, 5, 6} vs := mapFunc(vi, func(v int) string { return "<" + fmt.Sprint(v*v) + ">" }) fmt.Println(vs) } func findFunc[T comparable](a []T, v T) int { for i, e := range a { if e == v { return i } } return -1 } func main() { fmt.Println(findFunc([]int{1, 2, 3, 4, 5, 6}, 5)) }
Go语言学习笔记(一)
关键字
break
default
func
interface
select
case
defer
go
map
struct
chan
else
goto
package
switch
const
fallthrough
if
range
type
continue
for
import
return
var
基本类型,内置函数
append
bool
byte
cap
close
complex
complex64
complex128
uint16
copy
false
float32
float64
imag
int
int8
int16
uint32
int32
int64
iota
len
make
new
nil
panic
uint64
print
println
real
recover
string
true
uint
uint8
uintptr
可见性
首字母大写相当于public,小写相当于pivate
包重命名
package mainimport fm "fmt" // alias3func main() { fm.Println("hello, world") }
global virable
declare after package import
func params and return values
func Sum(a, b int) int { return a + b }func functionName(parameter_list) (return_value_list) { … }//????????????func (t T) Method1() { //...}
parameter_list 的形式为 (param1 type1, param2 type2, …)
return_value_list 的形式为 (ret1 type1, ret2 type2, …)
常量定义
可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。常量的定义格式const identifier [type] = value
显式类型定义:
const b string = "abc"
隐式类型定义:
const b = "abc"
常量的值必须是能够在编译时就能够确定的,数字型的常量是没有大小和符号的,并且可以使用任何精度而不会导致溢出
变量
var a, b *intvar a intvar b boolvar str stringvar ( a int b bool str string)
变量被声明之后,系统自动赋予它该类型的零值:int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil。
编译时即可推断类型
var identifier [type] = valuevar a int = 15var i = 5var b bool = falsevar str string = "Go says hello to the world!"//编译时推断类型var a = 15var b = falsevar str = "Go says hello to the world!"var ( a = 15 b = false str = "Go says hello to the world!" numShips = 50 city string )//运行时自动推断var ( HOME = os.Getenv("HOME") USER = os.Getenv("USER") GOROOT = os.Getenv("GOROOT") )
函数内部变量首字母大写全局or局部:函数内部变量均为局部
基本类型何运算符
数字类型
整数
int8(-128 -> 127)
int16(-32768 -> 32767)
int32(-2,147,483,648 -> 2,147,483,647)
int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807)
无符号整数
uint8(0 -> 255)
uint16(0 -> 65,535)
uint32(0 -> 4,294,967,295)
uint64(0 -> 18,446,744,073,709,551,615)
浮点型(IEEE-754 标准)
float32(+- 1e-45 -> +- 3.4 * 1e38)
float64(+- 5 1e-324 -> 107 1e308)
float32 精确到小数点后 7 位,float64 精确到小数点后 15 位。通过增加前缀 0 来表示 8 进制数(如:077),增加前缀 0x 来表示 16 进制数(如:0xFF),以及使用 e 来表示 10 的连乘(如: 1e3 = 1000,或者 6.022e23 = 6.022 x 1e23)。
package mainimport "fmt"func main() { var n int16 = 34 var m int32 // compiler error: cannot use n (type int16) as type int32 in assignment //m = n m = int32(n) fmt.Printf("32 bit int is: %d\n", m) fmt.Printf("16 bit int is: %d\n", n) }
格式化输出%d
用于格式化整数(%x
和 %X
用于格式化 16 进制表示的数字),%g
用于格式化浮点型(%f
输出浮点数,%e
输出科学计数表示法),%0d
用于规定输出定长的整数,其中开头的数字 0 是必须的。%n.mg
用于表示数字 n 并精确到小数点后 m 位,除了使用 g 之外,还可以使用 e 或者 f,例如:使用格式化字符串 %5.2e
来输出 3.4 的结果为 3.40e+00
。
类型转换
func IntFromFloat64(x float64) int { if math.MinInt32 <= x && x <= math.MaxInt32 { // x lies in the integer range whole, fraction := math.Modf(x) if fraction >= 0.5 { whole++ } return int(whole) } panic(fmt.Sprintf("%g is out of the int32 range", x)) }
运算符优先级
优先级 运算符 7 ^ ! 6 * / % << >> & &^ 5 + - | ^ 4 == != < <= >= > 3 <- 2 && 1 ||
字符串
解释字符串
该类字符串使用双引号括起来,其中的相关的转义字符将被替换,这些转义字符包括:
\n:换行符\r:回车符\t:tab 键\u 或 \U:Unicode 字符\\:反斜杠自身
非解释字符串
该类字符串使用反引号括起来,支持换行,例如:This is a raw string \n
中的 \n
会被原样输出。
在循环中使用加号+拼接字符串并不是最高效的做法,更好的办法是使用函数 strings.Join(),有没有更好地办法了?有!使用字节缓冲(bytes.Buffer)拼接更加给力!
字符串操作strings和strconv包
//前缀和后缀strings.HasPrefix(s, prefix string) boolstrings.HasSuffix(s, suffix string) bool//包含关系strings.Contains(s, substr string) boolstrings.ContainsAny(s, chars string) boolstrings.ContainsRune(s string, r rune) bool//索引strings.Index(s, str string) intstrings.LastIndex(s, str string) intstrings.IndexRune(s string, r rune) int//替换strings.Replace(str, old, new, n) string//次数统计strings.Count(s, str string) int//重复次数strings.Repeat(s, count int) string//大小写strings.ToLower(s) stringstrings.ToLower(s) string//修剪字符串func Trim(s string, cutset string) stringfunc TrimLeft(s string, cutset string) stringfunc TrimRight(s string, cutset string) stringfunc TrimSpace(s string) string//分割字符串strings.Fields(s) []stringstrings.Split(s, sep string) []string//拼接字符串strings.Join(sl []string, sep string) string
日期和时间
func timeTest(){ t := time.Now() fm.Println(t) fm.Println(t.Day(), t.Hour(), t.Minute()) t = time.Now().UTC() fm.Println(t) fm.Println(t.Format(time.RFC822)) fm.Println(t.Format(time.ANSIC)) fm.Println(t.Format("02 Jan 2006 15:04")) }
指针
//声明方式var intP *int//使用方式intP = &i1
不允许指针运算pointer+2
、c=*p++
,空指针的反向引用是不合法。
控制结构
if-esle语句
switch语句
不需要break,执行完分支自动退出switch,如果需要继续执行则使用fallthrough
switch num1 {case 98, 99: fmt.Println("It's equal to 98")case 100: fmt.Println("It's equal to 100")default: fmt.Println("It's not equal to 98 or 100") }//多条件同行,fallthrough,return跳出func switchStruct() int { a := 4 switch a { case 1,2,3: fmt.Println(a) case 4: fallthrough case 5: fm.Println("break") return 1 default: fmt.Println("ALL") } return 0}//bool用法switch { case num1 < 0: fmt.Println("Number is negative") case num1 > 0 && num1 < 10: fmt.Println("Number is between 0 and 10") default: fmt.Println("Number is 10 or greater") }//初始化用法switch a, b := x[i], y[j]; { case a < b: t = -1 case a == b: t = 0 case a > b: t = 1}
for语句
func forStatement() { for i := 1; i < 15; i++{ fm.Println(i) } j := 1 GO: fm.Println(j) j++ if j < 15{ goto GO } for k := 0; k < 5; k++ { for m := 0; m < k; m++ { fm.Printf("G") } fm.Println() } str := "GGGGGGGGGGGGGGGGGGGGGGGG" for i := 0; i < 5; i++ { fm.Println(str[0:i+1]) } str1 := "G" for i := 0; i < 5; i++ { fm.Println(str1) str1 += "G" } }
函数
Go 里面有三种类型的函数:
普通的带有名字的函数
匿名函数或者lambda函数
方法(Methods)
defer关键字
在函数return之后执行相关语句,多个defer逆序执行
func a() { i := 0 defer fmt.Println(i) i++ return}//输出0,输出语句之前的值
关闭文件流:
// open a file defer file.Close() (详见第 12.2 节)解锁一个加锁的资源
mu.Lock() defer mu.Unlock() (详见第 9.3 节)打印最终报告
printHeader() defer printFooter()关闭数据库链接
// open a database connection defer disconnectFromDB()
func testDefer() { fmt.Printf("In function1 at the top\n") defer func2() fmt.Printf("In function1 at the bottom!\n") i := 0 i++ defer fmt.Println(i) i++ for i := 0; i < 5; i++ { defer fmt.Printf("%d", i) } defer deferC(deferA(), deferB()) fm.Println("Begin:\n") return}func deferA() (rtn string) { fm.Println("in A") rtn = "AA" tmp := "tmp1" defer func() { fm.Println(tmp) }() defer fm.Println(rtn) tmp = "tmp2" return "AAAA"}func deferB() string { fm.Println("in B") return "B"}func deferC(str1, str2 string) { fm.Println("in C") }/*输出 In function1 at the top In function1 at the bottom! in A AA tmp2 in B Begin: in C 432101 function2: Deferred until the end of the calling function! */
defer
函数中的函数(defer deferC(deferA(), deferB())
中的A
和B
)会先于return执行,defer func()
可以rentun
语句执行完变量的最新的值,无func()
时则函数参数取对应defer
语句执行完变量的最新值。
内置函数
递归函数
函数作参数
func main() { callback(1, Add) } func Add(a, b int) { fmt.Printf("The sum of %d and %d is: %d\n", a, b, a+b)} func callback(y int, f func(int, int)) { f(y, 2) // this becomes Add(1, 2)}
闭包
func main() { var f = Adder() fmt.Print(f(1), " - ") fmt.Print(f(20), " - ") fmt.Print(f(300)) }func Adder() func(int) int { var x int return func(delta int) int { x += delta return x } }// 1,21,321
数组与切片
数组
Go 语言中的数组是一种值类型(不像 C/C++ 中是指向首元素的指针),所以可以通过 new() 来创建: var arr1 = new([5]int)。arr1 的类型是 *[5]int,而 arr2的类型是 [5]int。结果就是当把一个数组赋值给另一个时,需要在做一次数组内存的拷贝操作。例如:
arr2 := arr1arr2[2] = 100
两个数组就有了不同的值,在赋值后修改 arr2 不会对 arr1 生效。
func f(a [3]int) { fmt.Println(a) } func fp(a *[3]int) { fmt.Println(a) } func main() { var ar [3]int f(ar) // passes a copy of ar fp(&ar) // passes a pointer to ar}
当数组赋值时,发生了数组内存拷贝。
切片
切片是引用,所以它们不需要使用额外的内存并且比使用数组更有效率。
var identifier []typevar slice1 []type = arr1[start:end]var slice1 []type = arr1[:] //全部slice1 = &arr1 //全部
一个由数字 1、2、3 组成的切片可以这么生成:
s := [3]int{1,2,3}
甚至更简单的s := []int{1,2,3}
。数组区别:数组是切片的特例
buffer 串联字符串
创建一个 buffer,通过 buffer.WriteString(s)
方法将字符串 s 追加到后面,最后再通过 buffer.String()
方法转换为 string,这种实现方式比使用 +=
要更节省内存和 CPU。
var buffer bytes.Bufferfor { if s, ok := getNextString(); ok { //method getNextString() not shown here buffer.WriteString(s) } else { break } } fmt.Print(buffer.String(), "\n")
func AppendByte(slice []byte, data ...byte) []byte { m := len(slice) n := m + len(data) if n > cap(slice) { // if necessary, reallocate // allocate double what's needed, for future growth. newSlice := make([]byte, (n+1)*2) copy(newSlice, slice) slice = newSlice } slice = slice[0:n] copy(slice[m:n], data) return slice }
for-range
seasons := []string{"Spring", "Summer", "Autumn", "Winter"}//index&valfor ix, season := range seasons { fmt.Printf("Season %d is: %s\n", ix, season) }//just valvar season stringfor _, season = range seasons { fmt.Printf("%s\n", season) }//just indexfor ix := range seasons { fmt.Printf("%d", ix) }
复制与追加
func main() { // count number of characters: str1 := "asSASA ddd dsjkdsjs dk" fmt.Printf("The number of bytes in string str1 is %d\n",len(str1)) fmt.Printf("The number of characters in string str1 is %d\n",utf8.RuneCountInString(str1)) str2 := "asSASA ddd dsjkdsjsこん dk" fmt.Printf("The number of bytes in string str2 is %d\n",len(str2)) fmt.Printf("The number of characters in string str2 is %d",utf8.RuneCountInString(str2)) }/* Output: The number of bytes in string str1 is 22 The number of characters in string str1 is 22 The number of bytes in string str2 is 28 The number of characters in string str2 is 24 *///将一个字符串追加到某一个字符数组的尾部var b []bytevar s stringb = append(b, s...)
Go 语言中的字符串是不可变的,必须先将字符串转换成字节数组,然后再通过修改数组中的元素值来达到修改字符串的目的,最后将字节数组转换回字符串格式。
s := "hello"c := []byte(s) c[0] = ’c’ s2 := string(c) // s2 == "cello"
append操作
将切片 b 的元素追加到切片 a 之后:
a = append(a, b...)
复制切片 a 的元素到新的切片 b 上:
b = make([]T, len(a))copy(b, a)
删除位于索引 i 的元素:
a = append(a[:i], a[i+1:]...)
切除切片 a 中从索引 i 至 j 位置的元素:
a = append(a[:i], a[j:]...)
为切片 a 扩展 j 个元素长度:
a = append(a, make([]T, j)...)
在索引 i 的位置插入元素 x:
a = append(a[:i], append([]T{x}, a[i:]...)...)
在索引 i 的位置插入长度为 j 的新切片:
a = append(a[:i], append(make([]T, j), a[i:]...)...)
在索引 i 的位置插入切片 b 的所有元素:
a = append(a[:i], append(b, a[i:]...)...)
取出位于切片 a 最末尾的元素 x:
x, a = a[len(a)-1], a[:len(a)-1]
将元素 x 追加到切片 a:
a = append(a, x)
垃圾回收
var digitRegexp = regexp.MustCompile("[0-9]+")func FindDigits(filename string) []byte { b, _ := ioutil.ReadFile(filename) return digitRegexp.Find(b) }// 通过拷贝避免小切片占用大内存(整个文件)func FindDigits(filename string) []byte { b, _ := ioutil.ReadFile(filename) b = digitRegexp.Find(b) c := make([]byte, len(b)) copy(c, b) return c }
error: rune is not a Type
:代码中又于rune重名的函数
Map
var map1 map[keytype]valuetype
不要使用 new,永远用 make 来构造 map
map类型是非线程安全的,并行访问map数据会出错。并且为了性能map没有锁机制
// map排序var ( barVal = map[string]int{"alpha": 34, "bravo": 56, "charlie": 23,"delta": 87, "echo": 56, "foxtrot": 12,"golf": 34, "hotel": 16, "indio": 87,"juliet": 65, "kili": 43, "lima": 98} )func main() { fmt.Println("unsorted:") for k, v := range barVal { fmt.Printf("Key: %v, Value: %v / ", k, v) } keys := make([]string, len(barVal)) i := 0 for k, _ := range barVal { keys[i] = k i++ } sort.Strings(keys) fmt.Println() fmt.Println("sorted:") for _, k := range keys { fmt.Printf("Key: %v, Value: %v / ", k, barVal[k]) } }
包(package)
regexp包
锁和sync包
big包
struct和method
可以使用 make() 的三种类型:slices / maps / channels(见第 14 章)
在一个结构体中对于每一种数据类型只能有一个匿名字段。
内嵌结构体
package mainimport "fmt"type A struct { ax, ay int } type B struct { A bx, by float32 } func main() { b := B{A{1, 2}, 3.0, 4.0} fmt.Println(b.ax, b.ay, b.bx, b.by) fmt.Println(b.A) }
定义方法的一般格式:func (recv receiver_type) methodName(parameter_list) (return_value_list) { ... }
结构体方法
type TwoInts struct { a int b int}func main() { two1 := new(TwoInts) two1.a = 12 two1.b = 10 fmt.Printf("The sum is: %d\n", two1.AddThem()) fmt.Printf("Add them to the param: %d\n", two1.AddToParam(20)) two2 := TwoInts{3, 4} fmt.Printf("The sum is: %d\n", two2.AddThem()) }func (tn *TwoInts) AddThem() int { return tn.a + tn.b }func (tn *TwoInts) AddToParam(param int) int { return tn.a + tn.b + param }
非结构体类型方法
type IntVector []intfunc (v IntVector) Sum() (s int) { for _, x := range v { s += x } return}func main() { fmt.Println(IntVector{1, 2, 3}.Sum()) // 输出是6}
类型和作用在它上面定义的方法可以不同文件,但是必须在同一个包里定义,除非定义别名:
type myTime struct { time.Time //anonymous field} func (t myTime) first3Chars() string { return t.Time.String()[0:3]} func main() { m := myTime{time.Now()} // 调用匿名Time上的String方法 fmt.Println("Full time now:", m.String()) // 调用myTime.first3Chars fmt.Println("First 3 chars:", m.first3Chars()) }/* Output: Full time now: Mon Oct 24 15:34:54 Romance Daylight Time 2011 First 3 chars: Mon */
指针方法和值方法都可以在指针或非指针上被调用
type List []intfunc (l List) Len() int { return len(l) }func (l *List) Append(val int) { *l = append(*l, val) }func main() { // 值 var lst List lst.Append(1) fmt.Printf("%v (len: %d)", lst, lst.Len()) // [1] (len: 1) // 指针 plst := new(List) plst.Append(2) fmt.Printf("%v (len: %d)", plst, plst.Len()) // &[2] (len: 1)}
并发访问对象
import “sync”type Info struct { mu sync.Mutex // ... other fields, e.g.: Str string}func Update(info *Info) { info.mu.Lock() // critical section: info.Str = // new value // end critical section info.mu.Unlock() }
内嵌方法
//内嵌方法测试 func methodTest() { point := &NamedPoint{Point{3, 4}, "name"} fm.Println(point.Abs(), point.Point.Abs()) }type Point struct { x, y float64 } func (p *Point)Abs() float64 { return math.Sqrt(p.x*p.x + p.y*p.y) }type NamedPoint struct { Point name string } func (p *NamedPoint)Abs() float64 { return p.Point.Abs() * p.Point.Abs() }
多重继承
type Camera struct{}func (c *Camera) TakeAPicture() string { return "Click"}type Phone struct{}func (p *Phone) Call() string { return "Ring Ring"}type CameraPhone struct { Camera Phone }func main() { cp := new(CameraPhone) fmt.Println("Our new CameraPhone exhibits multiple behaviors...") fmt.Println("It exhibits behavior of a Camera: ", cp.TakeAPicture()) fmt.Println("It works like a Phone too: ", cp.Call()) }
垃圾回收和SetFinalizer
内存状态
// fmt.Printf("%d\n", runtime.MemStats.Alloc/1024)// 此处代码在 Go 1.5.1下不再有效,更正为var m runtime.MemStatsruntime.ReadMemStats(&m) fmt.Printf("%d Kb\n", m.Alloc / 1024)
回收对象时,对象移除前操作:
runtime.SetFinalizer(obj, func(obj *typeObj))
接口和反射
不同的结构实现interface中定义的接口,通过接口变量自动识别对应结构来实现多态,所有类型必须完全实现interface中的所有函数
type Shaper interface { Area() float32}type Square struct { side float32}func (sq *Square) Area() float32 { return sq.side * sq.side }type Rectangle struct { length, width float32}func (r Rectangle) Area() float32 { return r.length * r.width }func main() { r := Rectangle{5, 3} // Area() of Rectangle needs a value q := &Square{5} // Area() of Square needs a pointer // shapes := []Shaper{Shaper(r), Shaper(q)} // or shorter shapes := []Shaper{r, q} fmt.Println("Looping through shapes for area ...") for n, _ := range shapes { fmt.Println("Shape details: ", shapes[n]) fmt.Println("Area of this shape is: ", shapes[n].Area()) } }
接口的实现通过指针则变量必须传入指针;接口可以嵌套。
类型判断
switch t := areaIntf.(type) { case *Square: fmt.Printf("Type Square %T with value %v\n", t, t) case *Circle: fmt.Printf("Type Circle %T with value %v\n", t, t) case nil: fmt.Printf("nil value: nothing to check?\n") default: fmt.Printf("Unexpected type %T\n", t) } //简洁版switch areaIntf.(type) { case *Square: // TODOcase *Circle: // TODO... default: // TODO}
也可用于测试变量是否实现某接口
type Stringer interface { String() string}if sv, ok := v.(Stringer); ok { fmt.Printf("v implements String(): %s\n", sv.String()) // note: sv, not v}
接口调用
指针方法可以通过指针调用
值方法可以通过值调用
接收者是值的方法可以通过指针调用,因为指针会首先被解引用
接收者是指针的方法不可以通过值调用,因为存储在接口中的值没有地址
类型 T 的可调用方法集包含接受者为 T 或 T 的所有方法集
类型 T 的可调用方法集包含接受者为 T 的所有方法
类型 T 的可调用方法集不包含接受者为 *T 的方法
自定义类型排序接口
package sort// 排序接口type Sorter interface { Len() int Less(i, j int) bool Swap(i, j int) }func Sort(data Sorter) { len := data.Len() for i := 1; i < len; i++{ for j := 0; j < len - i; j++ { if data.Less(j+1, j) { data.Swap(j+1, j) } } } }
func main(){ Sunday := day{0, "SUN", "Sunday"} Monday := day{1, "MON", "Monday"} Tuesday := day{2, "TUE", "Tuesday"} Wednesday := day{3, "WED", "Wednesday"} Thursday := day{4, "THU", "Thursday"} Friday := day{5, "FRI", "Friday"} Saturday := day{6, "SAT", "Saturday"} data2 := []day{Tuesday, Thursday, Wednesday, Sunday, Monday, Friday, Saturday, Thursday} array2 := dayArr(data2) sort.Sort(array2) for _, d := range data2 { fmt.Printf("%v ", d) } fmt.Printf("\n") fm.Println(array2) fmt.Printf("\n") }// 自己想到的实现方式type day struct { num int shortName string longName string}type dayArr []dayfunc (p dayArr)Len() int { return len(p) }func (p dayArr)Less(i, j int) bool { return p[i].num < p[j].num }func (p dayArr)Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func main(){ Sunday := day{0, "SUN", "Sunday"} Monday := day{1, "MON", "Monday"} Tuesday := day{2, "TUE", "Tuesday"} Wednesday := day{3, "WED", "Wednesday"} Thursday := day{4, "THU", "Thursday"} Friday := day{5, "FRI", "Friday"} Saturday := day{6, "SAT", "Saturday"} data3 := []*day{&Tuesday, &Thursday, &Wednesday, &Sunday, &Monday, &Friday, &Saturday} // 结构体初始化 array3 := dayArray{data3} sort.Sort(&array3) for _, d := range data3 { fmt.Printf("%v ", d) } fmt.Printf("\n") fm.Println(array3) fmt.Printf("\n") }// 作者实现方式type dayArray struct { data []*day } func (p *dayArray)Len() int { return len(p.data) } func (p *dayArray)Less(i, j int) bool { return p.data[i].num < p.data[j].num} func (p *dayArray)Swap(i, j int) { p.data[i], p.data[j]= p.data[j], p.data[i]}
两种方式对比:
第一种方式使用struct数组(切片更恰当)和变量,都是通过值拷贝;第二种通过struct指针数组和数组的指针,通过引用。故内存占用和调用消耗有差别。
暂时没想到,以后补充
构建通用类型或包含不同类型变量的数组
对于返回值未知的接口,可以通过返回空接口。
package mintype Miner interface { Len() int ElemIx(ix int) interface{} Less(i, j int) bool}func Min(data Miner) interface{} { min := data.ElemIx(0) for i:=1; i < data.Len(); i++ { if data.Less(i, i-1) { min = data.ElemIx(i) } } return min }type IntArray []intfunc (p IntArray) Len() int { return len(p) }func (p IntArray) ElemIx(ix int) interface{} { return p[ix] }func (p IntArray) Less(i, j int) bool { return p[i] < p[j] }type StringArray []stringfunc (p StringArray) Len() int { return len(p) }func (p StringArray) ElemIx(ix int) interface{} { return p[ix] }func (p StringArray) Less(i, j int) bool { return p[i] < p[j] }
复制数据切片至空接口切片
内存布局不一样,需要一个个赋值
var dataSlice []myType = FuncReturnSlice()var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))for ix, d := range dataSlice { interfaceSlice[ix] = d }
通用节点数据结构
type Node struct { le *Node data interface{} ri *Node} func NewNode(left, right *Node) *Node { return &Node{left, nil, right} } func (n *Node) SetData(data interface{}) { n.data = data }
接口和动态类型
函数重载
通过函数参数...T
实现,或空接口fmt.Printf(format string, a ...interface{}) (n int, errno error)
接口继承
当一个类型包含(内嵌)另一个类型(实现了一个或多个接口)的指针时,这个类型就可以使用(另一个类型)所有的接口方法。
type Task struct { Command string *log.Logger}func NewTask(command string, logger *log.Logger) *Task { return &Task{command, logger} }task.Log() //多重继承type ReaderWriter struct { *io.Reader *io.Writer}
类型转换问题
对空接口interface{}进行强制类型转换报错
Conversions are expressions of the form T(x) where T is a type and x is an expression that can be converted to type T.
A non-constant value x can be converted to type T in any of these cases:
x is assignable to T.
x's type and T have identical underlying types.
x's type and T are unnamed pointer types and their pointer base types have identical underlying types.
x's type and T are both integer or floating point types.
x's type and T are both complex types.
x is an integer or a slice of bytes or runes and T is a string type.
x is a string and T is a slice of bytes or runes.
转换是形如
T(x)
的表达式,其中T
是一种类型,而x
是能转换为类型T
的表达式
下面任何一种情况中非常量x都能转换为类型T
x可赋值给T
x的类型和T的底层类型一致
x的类型和T是匿名指针类型且它们的指针基类型的底层类型一致
x的类型和T都是整型或浮点型
x的类型和T都是复数类型
x是整数、bytes切片或runes,且T是string
x是string且T是bytes切片或runes
GO中的OO
OO 语言最重要的三个方面分别是:封装,继承和多态,在 Go 中它们是怎样表现的呢?
封装(数据隐藏):Go 把它从4层简化为了2层:
包范围内的:通过标识符首字母小写,对象只在它所在的包内可见
可导出的:通过标识符首字母大写,对象 对所在包以外也可见
继承:用组合实现:内嵌一个(或多个)包含想要的行为(字段和方法)的类型;多重继承可以通过内嵌多个类型实现
多态:用接口实现:某个类型的实例可以赋给它所实现的任意接口类型的变量。类型和接口是松耦合的,并且多重继承可以通过实现多个接口实现。
读写数据
输入输出
文件读写
inputFile, inputError := os.Open("input.dat")if inputError != nil { fmt.Printf("An error occurred on opening the inputfile\n" + "Does the file exist?\n" + "Have you got acces to it?\n") return // exit the function on error } defer inputFile.Close() inputReader := bufio.NewReader(inputFile)for { inputString, readerError := inputReader.ReadString('\n') if readerError == io.EOF { return } fmt.Printf("The input was: %s", inputString) }
读到字符串
func main() { inputFile := "products.txt" outputFile := "products_copy.txt" buf, err := ioutil.ReadFile(inputFile) if err != nil { fmt.Fprintf(os.Stderr, "File Error: %s\n", err) // panic(err.Error()) } fmt.Printf("%s\n", string(buf)) err = ioutil.WriteFile(outputFile, buf, 0644) // oct, not hex if err != nil { panic(err. Error()) } }
缓冲读取
buf := make([]byte, 1024) ... n, err := inputReader.Read(buf)if (n == 0) { break}
按列读取
func main() { file, err := os.Open("products2.txt") if err != nil { panic(err) } defer file.Close() var col1, col2, col3 []string for { var v1, v2, v3 string _, err := fmt.Fscanln(file, &v1, &v2, &v3) // scans until newline if err != nil { break } col1 = append(col1, v1) col2 = append(col2, v2) col3 = append(col3, v3) } fmt.Println(col1) fmt.Println(col2) fmt.Println(col3) }
写文件
func main () { outputFile, outputError := os.OpenFile("output.dat", os.O_WRONLY|os.O_CREATE, 0666) if outputError != nil { fmt.Printf("An error occurred with file opening or creation\n") return } defer outputFile.Close() outputWriter := bufio.NewWriter(outputFile) outputString := "hello world!\n" for i:=0; i<10; i++ { outputWriter.WriteString(outputString) } outputWriter.Flush() }
os.O_RDONLY:只读
os.O_WRONLY:只写
os.O_CREATE:创建:如果指定文件不存在,就创建该文件。
os.O_TRUNC:截断:如果指定文件已存在,就将该文件的长度截为0。
// 非缓冲写入func main() { os.Stdout.WriteString("hello, world\n") f, _ := os.OpenFile("test", os.O_CREATE|os.O_WRONLY, 0) defer f.Close() f.WriteString("hello, world in a file\n") }
文件拷贝
func CopyFile(dstName, srcName string) (written int64, err error) { src, err := os.Open(srcName) if err != nil { return } defer src.Close() dst, err := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE, 0644) if err != nil { return } defer dst.Close() return io.Copy(dst, src) }
读取参数
func main() { who := "Alice " if len(os.Args) > 1 { who += strings.Join(os.Args[1:], " ") } fmt.Println("Good Morning", who) }
JSON
JSON 与 Go 类型对应如下
bool 对应 JSON 的 booleans
float64 对应 JSON 的 numbers
string 对应 JSON 的 strings
nil 对应 JSON 的 null
不是所有的数据都可以编码为JSON类型:只有验证通过的数据结构才能被编码:
JSON 对象只支持字符串类型的 key;要编码一个 Go map 类型,map 必须是 map[string]T(T是json包中支持的任何类型)
Channel,复杂类型和函数类型不能被编码
不支持循环数据结构;它将引起序列化进入一个无限循环指针可以被编码,实际上是对指针指向的值进行编码(或者指针是 nil)
错误处理与测试
协程与通道
不要通过共享内存来通信,而通过通信来共享内存。
func main() { runtime.GOMAXPROCS(2) ch1 := make(chan int) ch2 := make(chan int) go pump1(ch1) go pump2(ch2) go suck(ch1, ch2) time.Sleep(1e9) }func pump1(ch chan int) { for i:=0; ; i++ { ch <- i*2 } }func pump2(ch chan int) { for i:=0; ; i++ { ch <- i+5 } }func suck(ch1,ch2 chan int) { for i := 0; ; i++ { select { case v := <- ch1: fmt.Printf("%d - Received on channel 1: %d\n", i, v) case v := <- ch2: fmt.Printf("%d - Received on channel 2: %d\n", i, v) } } }
协程间通信
通道声明:
var identifier chan typeid := make(chan type)// 缓冲通道iden := make(chan type, len)
流向通道(发送)
ch <- int1
表示:用通道 ch 发送变量int1(双目运算符,中缀 = 发送)从通道流出(接收),三种方式:
int2 = <- ch
表示:变量 int2 从通道 ch(一元运算的前缀操作符,前缀 = 接收)接收数据(获取新值)假设 int2 已经声明过了,如果没有的话可以写成:
int2 := <- ch
<- ch 可以单独调用获取通道的(下一个)值,当前值会被丢弃,但是可以用来验证,以下代码是合法的:
if <- ch != 1000{ ...}
数据传输
func TestChan() { ch := make(chan string) go sendData(ch) go recvData(ch) time.Sleep(1e9) }func sendData(ch chan string) { ch <- "hu" ch <- "yuan" ch <- "sky"}func recvData(ch chan string) { recv := "" for { recv = <-ch fmt.Println(recv) } }
阻塞
// 发送阻塞func main() { ch1 := make(chan int) go pump(ch1) // pump hangs time.Sleep(2*1e9) fmt.Println("Recv", <-ch1) // prints only 0 time.Sleep(1e9) }func pump(ch chan int) { for i := 1; ; i++ { ch <- i fmt.Println("Send ",i) } }//发送阻塞func main() { c := make(chan int) go func() { time.Sleep(15 * 1e9) x := <-c fmt.Println("received", x) }() fmt.Println("sending", 10) c <- 10 fmt.Println("sent", 10) }
信号量模式
循环并行执行
// 测试同步执行type Empty interface {}const N = 100000func Compare() { ch_buf := make(chan Empty, N) data := make([]float64, N) for i:=0; i < N; i++{ data[i] = float64(i) } s := time.Now() TestChannal(data, ch_buf) fmt.Println(time.Now().Sub(s)) //fmt.Println(data) for i:=0; i < N; i++{ data[i] = float64(i) } s = time.Now() TestNormal(data) fmt.Println(time.Now().Sub(s)) //fmt.Println(data)}func TestChannal(data []float64, ch_buf chan Empty) { var em Empty for ix, val :=range data { go func(ix int, val float64) { for j := 0; j < N; j++ { data[ix] += val } ch_buf <- em }(ix, val) } for i := 0; i < N; i++ { <-ch_buf} }func TestNormal(data []float64){ for ix, val := range data { for j := 0; j < N; j++ { data[ix] += val } } }
通道的方向
var send_only chan<- int // channel can only send datavar recv_only <-chan int // channel can onley recv data
只接收的通道(
<-chan T
)无法关闭,因为关闭通道是发送者用来表示不再给通道发送值了,所以对只接收通道是没有意义的。
网络、模板和网络应用
注意事项
所有的包名使用小写
如果对一个包进行更改或重新编译,所有引用了这个包的客户端程序都必须全部重新编译。
must define main function in main package else you will get error 'undefined'. main function has not params and return value
init function execute before main
production server must use function in package "fmt"
全局变量是允许声明但不使用
Go语言学习笔记:基础语法知识
变量声明与初始化
Go语言是静态类型语言,变量有明确=类型,编译器也会检查变量类型的正确性。变量声明标准格式如下:
�var name type // 关键字 变量名 变量类型
不同于C语言,Go变量类型在后面, 避免混淆,如var a,b int *
把a,b都声明称指针类型的变量。
变量初始化标准格式:var name type = value
备注:当一个变量被声明后,会自动赋予零值:int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil 等。所有的内存在 Go 中都是经过初始化的。
其他声明方式如下:
1. 批量声明:使用关键字 var 和括号,可以将一组变量定义放在一起。
var ( a int b string c float )
2. 简短格式
num := 10 //num = 10 I,j := 1,2 //I=1;j=2
简短格式有如下限制:1)定义变量,同时初始化;2)无需定义数据类型;3)只能在函数内用。简短格式广泛用于局部变量的声明和定义,var形式常用于需要显示定义变量类型的地方。
注意:在多个短变量声明和赋值中,至少有一个新声明的变量出现在左值中,即便其他变量名可能是重复声明的。
conn, err := net.Dial(“tcp”, “127.0.0.1:8080”) conn2, err := net.Dial(“tcp”, “127.0.0.1:8080”)
基本数据类型
* bool * string * int、int8、int16、int32、int64 * uint、uint8、uint16、uint32、uint64、uintptr * byte // uint8 的别名 * rune // int32 的别名 代表一个 Unicode 码 * float32、float64 * complex64、complex128
多重赋值
GO语言神奇的变量交换动作,多重赋值特性
var a int = 100 var b int = 200 a, b = b,a
匿名变量
没有名字的变量,表示没有用到可以忽略的变量,用_
空白标识符表示。它可以被声明和赋值,但不会用到。匿名变量不占用内存空间,不会分配内存。匿名变量与匿名变量之间也不会因为多次声明而无法使用。使用方法如教程中的示例代码:
func GetData() (int, int) { return 100, 200 } func main(){ a, _ := GetData() _, b := GetData() fmt.Println(a, b) }
变量作用域
Go语言会在编译时检查每个变量是否使用过,一旦出现未使用的变量,就会报编译错误。根据变量定义的位置,分为局部变量、全局变量、函数形参三种。
局部变量在函数内声明,作用域只在函数内,函数的参数(即形参)和返回值都是均属于局部变量,局部变量在函数被调用时创建,函数调用结束被销毁;
在函数体外声明的变量称之为全局变量,全局变量只需要在一个源文件中定义,就可以在所有源文件中使用,当然,不包含这个全局变量的源文件需要使用“import”关键字引入全局变量所在的源文件之后才能使用这个全局变量。全局变量声明必须以 var 关键字开头,如果想要在外部包中使用全局变量的首字母必须大写。Go语言程序中全局变量与局部变量名称可以相同,但是函数体内的局部变量会被优先考虑。
整型与浮点型
整型有有符号和无符号整型,包括int8、int16、int32 和 int64四种有符号整型和对应的uint8、uint16、uint32 和 uint64 四种无符号整数类型,分别对应8、16、32、64位大小的整数。
此外最常用的是int 和 uint,表示对应CPU系统的字长,即会根据系统表示范围在32bit和64bit间变化
浮点型有float32和float64两种精度,一个 float32 类型的浮点数可以提供大约 6 个十进制数的精度,而 float64 则可以提供约 15 个十进制数的精度。
浮点数在声明的时候可以只写整数部分或者小数部分,方式如下:
var e float32 = .717 //0.717 cosnt f = 1. //1 const planck = 6.62606957e-3 //普朗克常数
布尔类型
布尔类型值只有:true或false,常用与条件语句,与==、> 、<
等操作使用,也可以和&& 和 ||操作符结合(&&的优先级比||高)。
与C语言不同的一点: GO语言中的布尔类型不会隐式转换称0或1,反之亦然,必须显示转换:
//布尔转0、1 func btoi(b bool) int { if b { return 1 } return 0 } // 0/1转布尔 func itob(i int) bool { return i != 0 }
字符串
Go语言有字符串类型:字符串是 UTF-8 字符的一个序列。字符串是一种值类型,且值不可变,使用双引号””来定义字符串。
package main import ”fmt“ func main(){ var str = “hahahahha” fmt.Println(str) }
字符串拼接符“+” :s := s1 + s2,如
str := “hellow ” + “Go” str += “ world!”
字符类型
字符串的每个元素,类似C语言中的char类型,Go语言中有两种:
* byte型,uint8类型,代表ASCII码的一个字符(C语言的char)
* rune类型,代表一个UTF-8字符,等价于int32类型,处理中文等字符使用。
var ch byte = ‘A’ var ch2 byte = 65 var ch3 byte = ‘\x41’ Var ch4 byte = ‘\377’ var ch int = '\u0041' var ch2 int = '\u03B2' var ch3 int = '\U00101234'
类型转换
Go语言不存在隐式类型转换,所有转换都要显示声明:
a := 6.0 b := int(a)
类型转换只能在定义正确的情况下转换成功,例如从一个取值范围较小的类型转换到一个取值范围较大的类型(将 int16 转换为 int32)。当从一个取值范围较大的类型转换到取值范围较小的类型时(将 int32 转换为 int16 或将 float32 转换为 int),会发生精度丢失(截断)的情况。
只有相同底层类型的变量之间可以进行相互转换(如将 int16 类型转换成 int32 类型),不同底层类型的变量相互转换时会引发编译错误(如将 bool 类型转换为 int 类型).
指针类型
指针(pointer)在Go语言中可以被拆分为两个核心概念:
* 类型指针,允许对这个指针类型的数据进行修改,传递数据可以直接使用指针,而无须拷贝数据,类型指针不能进行偏移和运算。
* 切片,由指向起始元素的原始指针、元素数量和容量组成。
要明白指针,需要知道几个概念:指针地址、指针类型和指针取值。
指针类型:一个指针变量可以指向任何一个值得内存地址,指针变量指向一个4或8字节的地址,如果指针被定义没有复制,默认值是nil。
指针地址:每个变量在运行时都有一个地址,代表内存中的位置。GO语言也是通过&
获取变量的内存地址
ptr := &v
Tips: 变量、指针和地址三者的关系是,每个变量都拥有地址,指针的值就是地址。
指针取值:*
,取地址操作符&和取值操作符是一对互补操作符,&取出地址,根据地址取出地址指向的值。
变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:
* 对变量进行取地址操作使用&操作符,可以获得这个变量的指针变量。
* 指针变量的值是指针地址。
* 对指针变量进行取值操作使用*操作符,可以获得指针变量指向的原变量的值。
创建指针的另一种方法:new()函数,new() 函数可以创建一个对应类型的指针,创建过程会分配内存,被创建的指针指向默认值。
str := new(string) *str = “hellow go china” fmt.Println(*str)
常量
常量用关键字const
定义,用于存储不会改变的的数据,常量是在编译时被创建的,常量定义格式如下const name type = value
,比如:
const pi = 3.1415
Go语言定义时可省略类型说明符,编译器会根据值来推断其类型。
* 显式定义:const b string = “aaa”
* 隐式定义: const b = “aaa”
批量声明常量时,除了第一个外其他的都可以省略,如果省略初始表达式则表示使用前面的初始化表达式,对应类型也一样,如:
const ( a = 1 b c = 2 d ) fmt.Println(a,b,c,d) // 1 1 2 2
常量生成器 iota:用于生产一组相似规则初始化的常量,但不用每行都写一遍初始化表达式,在const声明时,第一行声明的常量 iota会被置0,然后么一行加1,例如:
type Weekday int const ( SUNDAY Weekday = iota //0 MONDAY // 1 TUESDAT // 2 WEDNESDAY // 3 THURSDAY //4 FRIDAY // 5 SATURDAY // 6 )
无类型常量:常量并没有一个明确的基础类型,编译器为这些没有明确的基础类型的数字常量提供比基础类型更高精度的算术运算,可以认为至少有 256bit 的运算精度。这里有六种未明确类型的常量类型,分别是无类型的布尔型、无类型的整数、无类型的字符、无类型的浮点数、无类型的复数、无类型的字符串。通过延迟明确常量的具体类型,不仅可以提供更高的运算精度,而且可以直接用于更多的表达式而不需要显式的类型转换。例如:
var x float32 = math.Pi var y float64 = math.Pi var z complex128 = math.Pi
类型别名
类型别名定义(GO 1.9之后版本):type TypeAlias = Type
。TypeAlias只是Type的别名,本质上是同一类型。下面例子a的类型是NewInt,a2的类型是int,别名只在代码中存在的,编译完成别名类型就不存在了。
// 将NewInt定义为int类型 type NewInt int var a NewInt //取别名 type IntAlias = int var a2 IntAlias
关键字与标识符
Go语言关键字有如下25个:

标识符是指Go语言对各种变量、方法、函数等的命名,需要由字母、下划线、和数字组成,且第一个必须是字符。需要注意的是Go语言中,变量、函数、常量的首字母大写,则表示它可以被其他包访问(类似java的public);如果是小写,则表示只能在本包中使用(类似java的private)。Go语言有如下特殊字符,不能当做变量使用。

Go语言学习整理
本文基于菜鸟教程,对于自己不明白的点加了点个人注解,对于已明确的点做了删除,可能结构不太清晰,看官们可移步Go语言教程
1 Go语言结构
当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。
起始的{不能单独成行(使用IDE为JetBrain的GoLand,Goland我使用的破解方法都不太好使,也可以直接在线编译运行,网站自行百度)
go语言基础学习笔记完整版
https://blog.csdn.net/qq_37475168/article/details/105264300
Go语言学习笔记(一)
https://blog.csdn.net/weixin_42348333/article/details/86682676