go的基础组成由以下几部分组成:
- 包声明
- 引入包
- 函数
- 变量
- 语句 & 表达式
- 注释
下面来看以下实例:
package main // 包声明
import "fmt" // 导入包
func main(){ // 函数
fmt.Println("hello world!") //输出语句
}
/*
我是多行注释
goland 的第一行必须是package 包名来声明这个文件属于哪个包
package main 代表这是一个可独立执行的go程序
goland的第二行必须是import 包名, 不然会自动添加到第二行
func main() 是一个函数,go语言的程序跟C一样,是从main函数开始执行(如果
有init()函数,那么就从init()函数开始执行)
go 的注释风格跟类C语言是一致的
当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,
如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程
序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小
写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对
象语言中的 protected )。
注意和C风格的语言不同的一点:
需要注意的是 { 不能单独放在一行!!!!!!
在 Go 程序中,一行代表一个语句结束。所以go并不用像类C语言一样需要分号
但是如果多条语句在一行出现,要用;进行分隔。
go的标识符规则同C语言
*/
Go 语言数据类型
- 布尔
- 数字
- 字符串
- 派生
在 Go 编程语言中,数据类型用于声明函数和变量。
数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存。
布尔型的值只可以是常量 true
或者 false
。
数字型:整型 int
和浮点型 float32
、float64
。
字符串:Go
的字符串是由单个字节连接起来的。Go
语言的字符串的字节使用 UTF-8
编码标识 Unicode
文本。
派生类型:
- (a) 指针类型(Pointer)
- (b) 数组类型
- © 结构化类型(struct)
- (d) Channel 类型
- (e) 函数类型
- (f) 切片类型
- (g) 接口类型(interface)
- (h) Map 类型
整数类型:
- uint8-无符号 8 位整型 (0 到 255)
- uint16-无符号 16 位整型 (0 到 65535)
- uint32-无符号 32 位整型 (0 到 4294967295)
- uint64-无符号 64 位整型 (0 到 18446744073709551615)
- int8-有符号 8 位整型 (-128 到 127)
- int16-有符号 16 位整型 (-32768 到 32767)
- int32-有符号 32 位整型 (-2147483648 到 2147483647)
- int64-有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)
浮点型:
- float32-IEEE-754 32位浮点型数
- float64-IEEE-754 64位浮点型数
- complex64-32 位实数和虚数
- complex128-64 位实数和虚数
其他类型:
- byte-类似 uint8
- rune-类似 int32
- uint-32 或 64 位
- int-与 uint 一样大小
- uintptr-无符号整型,用于存放一个指针
Go语言变量
变量声明规则
var identifier type
var 标识符 数据类型
var b bool;
变量声明的两种方式:
第一种,指定变量类型,如果没有初始化,则变量默认为零值。(什么叫零值?就是0、false、""
(空字符串) 之类的)
var v_name v_type
v_name = value
实例:
package main
import "fmt"
func main(){
var b bool
var i int
var s string
var s1 string
s1 = "faker is a nb"
fmt.Println(b,i,s,s1)
}
result:
false 0 faker is a nb
以下几种类型为 <nil>
:
var a *int
var a []int
var a map[string] int
var a chan int
var a func(string) int
var a error // error 是接口
第二种,根据值自行判定变量类型。
var v_name = value
实例:
package main
import "fmt"
func main(){
var k = 1
fmt.Println(k)
}
第三种,省略 var
, 注意 :=
左侧如果没有声明新的变量,就产生编译错误,格式:
v_name := value
实例:
package main
import "fmt"
func main() {
k := 1
// k := 2 这样会报错
k, v := 3, 2 // 这样就不会报错
fmt.Println(k, v)
}
同时声明多个变量:
//类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3
var vname1, vname2, vname3 = v1, v2, v3 // 和 python 很像,不需要显示声
明类型,自动推断
vname1, vname2, vname3 := v1, v2, v3 // 出现在 := 左侧的变量不应该是已经
被声明过的,否则会导致编译错误
// 这种因式分解关键字的写法一般用于声明全局变量
var (
vname1 v_type1
vname2 v_type2
)
实例:
package main
var x, y int
var ( // 这种因式分解关键字的写法一般用于声明全局变量
a int
b 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)
}
意思就是,只有带var
的才能在函数外(全局)进行声明。用:=
只能在函数体内进行声明。
声明变量方法总结:
#第一种
#单
var b bool
b = false
#多
var a,b,c bool
a,b,c = false,true,false
#第二种
#单
var b = 3
#多
var b,c,d = false,true,false
#第三种
#单
b := 2
#多
b,c,d := 1,2,3
#第一种和第二种方法没有使用限制
#第三种方法只能用于函数体内
#多个变量声明时,只要有1个变量是新变量,就不会有语法错误。
#如果其他变量已经被声明过,那么会变成赋值操作
值类型和引用类型
所有像 int
、float
、bool
和 string
这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值:
当使用等号 =
将一个变量的值赋值给另一个变量时,如:j = i
,实际上是在内存中将 i
的值进行了拷贝:
像C语言一样,你可以通过 &i
来获取变量 i
的内存地址,例如:0xf840000040
(每次的地址都可能不一样)。值类型的变量的值存储在栈内存中。
实例:
package main
import "fmt"
func main() {
a := 1
b := a
fmt.Println(&a,&b)
}
输出:
0xc000016058 0xc000016060
更复杂的数据通常会需要使用多个字,这些数据一般使用引用类型保存。
一个引用类型的变量 r1
存储的是 r1
的值所在的内存地址(数字),或内存地址中第一个字所在的位置。其实这跟C语言的指针就很像了。
这个内存地址为称之为指针,这个指针实际上也被存在另外的某一个字中。
同一个引用类型的指针指向的多个字可以是在连续的内存地址中(内存布局是连续的),这也是计算效率最高的一种存储形式;也可以将这些字分散存放在内存中,每个字都指示了下一个字所在的内存地址。(跟链表差不多)
当使用赋值语句 r2 = r1
时,只有**引用(地址)**被复制。
如果 r1
的值被改变了,那么这个值的所有引用都会指向被修改后的内容,在这个例子中,r2
也会受到影响。
简短形式,使用 := 赋值操作符
我们知道可以在变量的初始化时省略变量的类型而由系统自动推断,声明语句写上 var 关键字其实是显得有些多余了,因此我们可以将它们简写为 a := 50
或 b := false
。
a
和 b
的类型(int
和 bool
)将由编译器自动推断。
这是使用变量的首选形式,但是它只能被用在函数体内,而不可以用于全局变量的声明与赋值。使用操作符 :=
可以高效地创建一个新的变量,称之为初始化声明。
如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误。(Go语言强制不让写垃圾代码)
但是全局变量是允许声明但不使用。(可以在函数体或者函数外使用,也可以不适用,没有限制)
和python
一样,如果你想要交换两个变量的值,则可以简单地使用 a, b = b, a
,两个变量的类型必须是相同。
空白标识符 _
也被用于抛弃值,如值 5
在:_, b = 5, 7
中被抛弃。
_
实际上是一个只写变量,你不能得到它的值。这样做是因为 Go
语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。
并行赋值也被用于当一个函数返回多个返回值时,比如这里的 val
和错误 err
是通过调用 Func1
函数同时得到:val, err = Func1(var1)
。其实并行赋值就是python
的序列解包。
Go 语言常量
常量是一个简单值的标识符,在程序运行时,不会被修改的量。
常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
常量的定义格式:
const identifier [type] = value
你可以省略类型说明符 type
,因为编译器可以根据变量的值来推断其类型。
tips: []
代表是可选参数。跟linux
一样。
举例:
显式类型定义: const b string = "abc"
隐式类型定义: const b = "abc"
多个相同类型的声明可以简写为:
const c_name1, c_name2 = value1, value2
Demo:
package main
import "fmt"
func main() {
const LENGTH int = 10
const WIDTH int = 5
var area int
const a, b, c = 1, false, "str" //多重赋值
area = LENGTH * WIDTH
fmt.Printf("面积为 : %d", area)
println()
println(a, b, c)
}
常量还可以用作枚举:
const (
Unknown = 0
Female = 1
Male = 2
)
数字 0、1 和 2 分别代表未知性别、女性和男性。
常量可以用len(), cap(), unsafe.Sizeof()
函数计算表达式的值。常量表达式中,函数必须是内置函数,否则编译不过(想想为啥:大概是编译的时候就存在的函数只有内置函数,如果用自定义的函数,相当于直接在编译阶段对自定义函数进行了调用操作):
package main
import "unsafe"
const (
a = "abc"
b = len(a)
c = unsafe.Sizeof(a)
)
func main(){
println(a, b, c)
}
先来看个好玩的特性:
package main
import "fmt"
func main() {
const (
a = 100 //b、c、d都会自动变成100 即执行了b=100,c=100等操作
b
c
d
e = 1 //f、g、h、i都会自动变成1,即执行了f=1、g=1等操作
f
g
h
i
)
fmt.Println(a,b,c,d,e,f,g,h,i)
}
输出:
100 100 100 100 1 1 1 1 1
iota
iota,特殊常量,可以认为是一个可以被编译器修改的常量。
iota 在 const
关键字出现时将被重置为 0(const 内部的第一行之前),const
中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。
iota 可以被用作枚举值:
const (
a = iota
b = iota
c = iota
)
第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:
const (
a = iota
b
c
)
iota 用法
package main
import "fmt"
func main() {
const (
a = iota //0
b //1
c //2
d = "ha" //独立值,iota += 1
e //"ha" iota += 1
f = 100 //iota +=1
g //100 iota +=1
h = iota //7,恢复计数
i //8
)
fmt.Println(a,b,c,d,e,f,g,h,i)
}
以上实例运行结果为:
0 1 2 ha ha 100 100 7 8
再看个有趣的的 iota 实例:
package main
import "fmt"
const (
i=1<<iota
j=3<<iota
k # 自动补齐成3<<iota
l # 自动补齐成3<<iota
)
func main() {
fmt.Println("i=",i)
fmt.Println("j=",j)
fmt.Println("k=",k)
fmt.Println("l=",l)
}
结果:
i= 1
j= 6
k= 12
l= 24
iota 表示从 0 开始自动加 1,所以 i=1<<0, j=3<<1
(<<
表示左移的意思),即:i=1, j=6
,这没问题,关键在 k 和 l,从输出结果看 k=3<<2,l=3<<3
。
简单表述:
i=1:左移 0 位,不变仍为 1;
j=3:左移 1 位,变为二进制 110, 即 6;
k=3:左移 2 位,变为二进制 1100, 即 12;
l=3:左移 3 位,变为二进制 11000,即 24。