Go(又称 Golang)是 Google 的 Robert Griesemer,Rob Pike 及 Ken Thompson 开发的一种静态类型、编译型语言。Go 语言语法与 C语言相近,但功能上有:内存安全,GC(垃圾回收),结构形态及 CSP-style 并发计算。
Go语言是静态类型语言,因此变量(variable)是有明确类型的,编译器也会检查变量类型的正确性。
我们从计算机系统的角度来讲,变量就是一段或者多段内存,用于存储数据
var 变量名 变量类型
例如: var age int //声明了一个名为age的变量,类型为int
注意:变量声明以关键字var开头,变量类型后置,行尾无须分号
如果你学过C语言,就会体会到这样声明的好处,比如C语言这样声明:int* a, b ,那么只有a是指针,b不是,这样会使人迷惑,如果想
要两个变量都为指针,需要这样定义:int *a,*b
。 在go语言中,我们使用这样的声明方式:var a,b *int
,就可以轻松的将a,b都
声明为指针。变量的命名规则遵循驼峰命名法,即首个单词小写,每个新单词的首字母大写,例如: startDate
var b bool = true
。**当一个变量被声明之后,系统自动赋予它该类型的零值:**int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil
var level = 1; //并未声明level的数据类型,go语言编译时会根据所赋的值1,去自动推断level的数据类型
我们可以使用
fmt.Printf("%T", level) //输出 int , 此处的T就表示的是当前变量的类型
觉得每行都用 var 声明变量比较烦琐?Go语言提供了批量声明的方式
var (
a int
b string
c []float32
)
举个例子:
package main
import "fmt"
var (
a int
b string
c float32
)
func main() {
//%d 整数占位符,%s 字符串占位符, %f 浮点数占位符(默认精度为6)
fmt.Printf("%d,%s,%f",a,b,c) //输出: 0, , 0.000000
}
我们可以省略var关键字,这样写起来更便捷
i := 1 //声明一个变量 i 其初始值为1
上面讲过,如果不指明类型,直接赋值,Go会自动推导类型
使用简短格式有以下限制:
package main
import "fmt"
//不能
//aa := 1
func main() {
aa := 1
fmt.Println(aa)
}
简短变量声明被广泛用于大部分的局部变量的声明和初始化,var 形式的声明语句往往用于需要显式指定变量类型的地方
//方式一:声明并初始化,一般用于声明全局变量
var level int = 1
//方式二:短变量声明并初始化,一般是用于声明局部变量
i := 1
以下的代码会出错:
package main
func main() {
var level int = 1
// 再次声明并赋值 会报错 no new variables on left side of := (左边的变量已经被声明了,不能重复声明)
level := 1
}
但是有特例
比如:net.Dial
提供按指定协议和地址发起网络连接,这个函数有两个返回值,一个是连接对象(conn)
,一个是错误对象(err)
正常的写法:
package main
import (
"fmt"
"net"
)
func main() {
var conn net.Conn
var err error
conn, err = net.Dial("tcp", "127.0.0.1:8080")
fmt.Println(conn)
fmt.Println(err)
}
短变量的写法:
package main
import (
"fmt"
"net"
)
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:8080")
conn1, err := net.Dial("tcp", "127.0.0.1:8080")
fmt.Println(conn)
fmt.Println(conn1)
fmt.Println(err)
}
在多个短变量声明和赋值中,至少有一个新声明的变量出现在左值中(与顺序),即便其他变量名可能是重复声明的,编译器也不会报错
使用多重赋值
时,如果不需要在左值中接受变量
,可以使用匿名变量
//向具体网址发起连接请求,如果请求成功,连接对象返回给conn,连接失败将失败对象返回给err
conn, _ := net.Dial("tcp", "127.0.0.1:8080") // _表示匿名变量:相当于把这个变量抛弃了,不接收Dial返回的结果
conn1, _ := net.Dial("tcp", "127.0.0.1:8080") // 匿名变量不可以直接开头
fmt.Println(conn)
fmt.Println(conn1)
匿名变量以“_”下划线表示
匿名变量不占用命名空间,也不会分配内存。匿名变量可以重复声明使用
“_”本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。
一个变量(常量、类型或函数)在程序中都有一定的作用范围,称之为作用域
。
了解变量的作用域对我们学习Go语言来说是比较重要的,因为Go语言(静态语言)会在编译时检查每个变量是否使用过,一旦出现未使用的变量,就会报编译错误。如果不能理解变量的作用域,就有可能会带来一些不明所以的编译错误。
根据变量定义位置的不同,可以分为以下三个类型:
在函数体内声明的变量称之为局部变量
,它们的作用域只在函数体内
,函数的参数和返回值变量都属于局部变量。
局部变量不是一直存在的,它只在定义它的函数被调用后存在,函数调用结束后这个局部变量就会被销毁。
package main
import (
"fmt"
)
func main() {
//声明局部变量 a 和 b 并赋值
var a int = 3
var b int = 4
//声明局部变量 c 并计算 a 和 b 的和
c := a + b
fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c)
}
在函数体外声明的变量称之为全局变量
,全局变量只需要在一个源文件中定义,就可以在所有源文件中使用
,当然,不包含这个全局变量的源
文件需要使用“import”关键字引入全局变量所在的源文件之后才能使用这个全局变量。
全局变量声明必须以 var 关键字开头
,如果想要在外部包中使用全局变量的首字母必须大写
package main
import "fmt"
//声明全局变量
var c int
func main() {
//声明局部变量
var a, b int
//初始化参数
a = 3
b = 4
c = a + b
fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c)
}
Go语言程序中全局变量与局部变量名称可以相同,但是函数体内的局部变量会被优先考虑。
package main
import "fmt"
//声明全局变量
var bb float32 = 3.14
func main() {
bb := 3 //声明同名局部变量
fmt.Println(bb)
}
//执行结果 3
在定义函数时函数名后面括号中的变量叫做形式参数
(简称形参)。形式参数只在函数调用时才会生效,函数调用结束后就会被销毁,在
函数未被调用时,函数的形参并不占用实际的存储单元
,也没有实际值。
形式参数会作为函数的局部变量来使用
。
package main
import "fmt"
a := 1
b := 2
c := sum(a, b)
fmt.Println(c)
}
func sum(a, b int) int { //a , b 即为形式参数
return a + b
}
//执行结果 3
至此,Go语言变量相关的知识,我们就掌握了
Go语言同时提供了有符号和无符号的整数类型。
-2^(n-1) 到 2^(n-1)-1
无符号整型范围: 0 到 2^n-1
实际开发中由于编译器和计算机硬件的不同,int 和 uint 所能表示的整数大小会在 32bit 或 64bit 之间变化。
因此,在二进制传输、读写文件的结构描述时,为了保持文件的结构不会受到不同编译目标平台字节长度的影响,不要使用 int 和 uint
Go语言支持两种浮点型数:
float32
: 范围 约1.4e-45 到 约3.4e38float64
:范围约4.9e-324 到 约1.8e308floatStr1 := 3.2
//保留小数点位数
fmt.Printf("%.2f\n", floatStr1)
算术规范由 IEEE754 浮点数国际标准定义,该浮点数规范被所有现代的 CPU 支持
通常应该优先使用 float64 类型,因为 float32 类型的累计计算误差很容易扩散,并且 float32 能精确表示的正整数并不是很大。
var f float32 = 1 << 24;
fmt.Println(f == f+1) // true
浮点数在声明的时候可以只写整数部分或者小数部分
var e = .71828 // 0.71828
var f = 1. // 表示我们的f是浮点类型,值为1
fmt.Printf("%.5f,%.1f",e,f)
很小或很大的数最好用科学计数法书写,通过 e 或 E 来指定指数部分
var avogadro = 6.02214129e23 // 阿伏伽德罗常数
var planck = 6.62606957e-34 // 普朗克常数
fmt.Printf("%f,%.35f",avogadro,planck)
在Go语言中,以bool类型进行声明:
var 变量名 bool
==
,>
,<
,<=
, >=
,&&(AND)
,||(OR)
等都会产生bool值
var aVar = 10
aVar == 5 // false
aVar == 10 // true
aVar != 5 // true
aVar != 10 // false
比较之间的条件:
常量
,那么另外一个值可以不是常量,但是类型必须和该常量类型相同。&&(AND)
,||(OR)
是具有短路行为的,如果运算符左边的值已经可以确定整个布尔表达式的值,那么运算符右边的值将不再被求值。(&&优先级高于||)
var a = 10
//因为a>11已经不满足了,所以a < 30不会走,整个表达式为false
if(a > 11 && a < 30){
fmt.Println("正确")
}else{
fmt.Println("错误")
}
//因为a > 5已经满足了,所以a < 30不会走,整个表达式为true
if(a > 5 || a < 30){
fmt.Println("正确")
}else{
fmt.Println("错误")
}
布尔型数据只有true和false,且不能参与任何计算以及类型转换
字符串中的每一个元素叫做“字符”,在遍历或者单个获取字符串元素时可以获得字符。
Go语言的字符有以下两种:
byte 类型是 uint8 的别名,对于只占用 1 个字节的传统 ASCII 编码的字符来说,完全没有问题,例如 var ch byte = 'A'
,字符使用单引号括起来。而占用两个字节的字符需要使用int32(rune) 例如:var c2 rune = '我'
字符定义的其他方式:
//使用单引号 表示一个字符
var ch byte = 'A'
//在 ASCII 码表中,A 的值是 65,也可以这么定义
var ch byte = 65
//65使用十六进制表示是41,所以也可以这么定义 \x 总是紧跟着长度为 2 的 16 进制数
var ch byte = '\x41'
//65的八进制表示是101,所以使用八进制定义 \后面紧跟着长度为 3 的八进制数
var ch byte = '\101'
fmt.Printf("%c",ch)
Unicode 是 ASCII 的超集,它定义了 1,114,112 个代码点的代码空间。 Unicode 版本 10.0 涵盖 139 个现代和历史文本集(包括符文字母,但不包括 Klingon )以及多个符号集。
Go语言同样支持 Unicode(UTF-8), 用rune来表示
, 在内存中使用 int 来表示。
在书写 Unicode 字符时,需要在 16 进制数之前加上前缀\u
或者\U
。如果需要使用到 4 字节,则使用\u
前缀,如果需要使用到 8 个字节,则使用\U
前缀。
var ch rune = '\u0041'
var ch1 int64 = '\U00000041'
//格式化说明符%c用于表示字符,%v或%d会输出用于表示该字符的整数,%U输出格式为 U+hhhh 的字符串。
fmt.Printf("%c,%c,%U",ch,ch1,ch)
Unicode 包中内置了一些用于测试字符的函数,这些函数的返回值都是一个布尔值,如下所示(其中 ch 代表字符):
UTF-8 和 Unicode 有何区别?
Unicode 与 ASCII 类似,都是一种字符集。
字符集为每个字符分配一个唯一的 ID,我们使用到的所有字符在 Unicode 字符集中都有一个唯一的 ID,例如上面例子中的 a 在 Unicode 与 ASCII 中的编码都是 97。汉字“你”在 Unicode 中的编码为 20320,在不同国家的字符集中,字符所对应的 ID 也会不同。而无论任何情况下,Unicode 中的字符的 ID 都是不会变化的。
UTF-8 是编码规则,将 Unicode 中字符的 ID 以某种方式进行编码,UTF-8 的是一种变长编码规则,从 1 到 4 个字节不等。
编码规则如下:
根据这个规则,拉丁文语系的字符编码一般情况下每个字符占用一个字节,而中文每个字符占用 3 个字节。
广义的 Unicode 指的是一个标准,它定义了字符集及编码规则,即 Unicode 字符集和 UTF-8、UTF-16 编码等。
一个字符串是 一经定义,一个不可改变的字节序列,字符串可以包含任意的数据,但是通常是用来包含可读的文本,字符串是 UTF-8
字符的一个序列。 注意:字符串底层是byte数组
字符串的定义:
var mystr string = "hello"
字符串是一种值类型,且值不可变,即创建某个文本后将无法再次修改这个文本的内容。
当字符为 ASCII 码表上的字符时则占用 1 个字节,如果是unicode编码表上就是3个字节
字符串中可以使用转义字符来实现换行、缩进等效果,常用的转义字符包括:
\n:
换行符\r:
回车符\t:
tab 键\u 或 \U:
Unicode 字符var str = "hello,golang\nNice to meet you"
var str_1 = `hello,golang
Nice to meet you`
fmt.Print(str) // 换行输出
fmt.Print(str_1) //同样换行输出
字符串是字节的定长数组,byte 和 rune 都是字符类型,若多个字符放在一起,就组成了字符串
比如 hello
,对照 ascii 编码表,每个字母对应的编号是:104,101,108,108,111
import (
"fmt"
)
func main() {
var mystr01 string = "hello"
var mystr02 [5]byte = [5]byte{104, 101, 108, 108, 111}
fmt.Printf("myStr01: %s\n", mystr01)
fmt.Printf("myStr02: %s", mystr02)
}
思考:hello,码神之路 占用几个字节 ?
package main
import (
"fmt"
)
func main() {
//中文三字节,字母一个字节
var myStr01 string = "hello,码神之路"
fmt.Printf("mystr01: %d\n", len(myStr01)) // mystr01:18
}
一般的比较运算符(==、!=、<、<=、>=、>)是通过在内存中按字节比较来实现字符串比较的,因此比较的结果是字符串自然编码的顺序。
字符串所占的字节长度可以通过函数 len() 来获取,例如 len(str)。
字符串的内容(纯字节)可以通过标准索引法来获取,在方括号[ ]
内写入索引,索引从 0 开始计数:
需要注意的是,这种转换方案只对纯 ASCII 码的字符串有效。
注意:获取字符串中某个字节的地址属于非法行为,例如 &str[i]。
ASCII字符使用len()
函数
Unicode字符串长度使用utf8.RuneCountInString()
函数
//如何计算字符串的长度
str3 := "hello"
str4 := "你好"
fmt.Println(len(str3)) // 1个字母占1个字节
fmt.Println(len(str4)) // 1个中文占3个字节,go从底层支持utf8
fmt.Println(utf8.RuneCountInString(str4)) // 2
**方式一:**两个字符串 s1 和 s2 可以通过 s := s1 + s2 拼接在一起。将 s2 追加到 s1 尾部并生成一个新的字符串 s。
//因为编译器会在行尾自动补全分号,所以拼接字符串用的加号“+”必须放在第一行末尾。
str := "第一部分 " +
"第二部分"
**方式二:**也可以使用“+=”来对字符串进行拼接:
s := "hel" + "lo,"
s += "world!"
fmt.Println(s) //输出 “hello, world!”
**方式三:**除了使用+
进行拼接,我们也可以使用WriteString()
//字符串拼接:方式二(效率会更加高一些)
s1 := "hello"
s2 := "golang"
var stringBuffer bytes.Buffer // 类似JAVA当中的个StringBuffer,相当于一个字符串缓冲区,可以减少字符串的创建次数
//节省内存分配,提高处理效率
stringBuffer.WriteString(s1)
stringBuffer.WriteString(s2)
fmt.Println(stringBuffer.String()) // hello golang
思考:如果从字符串 hello 语言之王
中获取 语
该如何获取呢?
str1 := "hello"
u := str1[0]
fmt.Printf("%c \n", u) //获取hello中的第一个字节内容
str2 := "hello 语言之王"
fmt.Printf("%c \n", str2[0]) //获取str2中的第一个字节内容
fmt.Printf("%c \n", []rune(str2)[6]) // 先将字符串转换为rune数组,获取数组的第6位
str1 := “hello” str2 := “hello 语言之王”
遍历 str1 , str2
//1、传统for循环便利ascii码字符串 str1
for i := 0; i < len(str1); i++ {
fmt.Printf("%c ", str1[i])
}
//2、for range 遍历ascii字符串 str1
for _, s := range str1 {
fmt.Printf("%c ", s)
}
//3、for range 遍历unicode字符串 str2
for _, s := range str2 {
fmt.Printf("%c ", s)
}
print :
结果写到标准输出Sprint:
结果会以字符串形式返回Println:
换行输出Printf:
按照指定格式输出res := fmt.Sprintf("%c \n", []rune(str2)[6]) //Sprint会将打印的结果转换为字符串然后返回给res
fmt.Println(res)
%c 单一字符
%T 动态类型
%v 本来值的输出
%+v 字段名+值打印
%d 十进制打印数字
%p 指针,十六进制
%f 浮点数
%b 二进制
%s string
如何获取字符串中的某一段字符?
tracer := "go语言天下第一,java表示并不服气"
//正向搜索字符串
comma := strings.Index(tracer, ",") //得到,的下标
fmt.Println(",所在位置:", comma)
fmt.Println(tracer[comma+1:]) //打印出,以后的所有字符
add := strings.Index(tracer, "+") //得到 + 的下标 , 如果不存在那么返回的值为-1
fmt.Println("+所在位置:", add)
Golang语言的字符串是不可变的
,修改字符串时,可以将字符串转换为[]byte
进行修改
[]byte和string可以通过强制类型转换 , 类型转换相关介绍查看:5.6数据类型的转换
案例:案例:将8080改为8081
url := "localhost:8080"
//url[len(url)-1] = 1 //字符串一经定义无法改变,因此这种方式无法修改
b := []byte(url)
b[len(b)-1] = '1' //这里注意是替换的字符1
url2 := string(b)
fmt.Println(url2)
在必要以及可行的情况下,一个类型的值可以被转换成另一种类型的值。由于Go语言不存在隐式类型转换,因此所有的类型转换都必
须显式的声明 注意:(其数据类型转换的语法规则跟Java的数据类型转换差不多)
//类型 B 的值 = 类型 B(类型 A 的值)
valueOfTypeB = type B(valueOfTypeA)
示例:
a := 5.0
b := int(a)
一些情况
精度丢失
的情况。字符串替换, 比如将 “Hello, 码神之路Java教程” 替换为 “Hello, 码神之路Go教程”
思路:
// 将Hello,码神之路Java教程 修改为 Hello,码神之路GO教程
target := "Hello,码神之路Java教程"
source := "Java"
newans := "GO"
pos := strings.Index(target, source)
start := target[:pos]
end := target[pos+len(source):]
var stringBuilder bytes.Buffer //定义一个字符串缓冲区,实现字符串拼接
stringBuilder.WriteString(start)
stringBuilder.WriteString(newans)
stringBuilder.WriteString(end)
fmt.Println(stringBuilder.String()) // Hello,码神之路GO教程
Go语言中的常量使用关键字const
定义,用于存储不会改变的数据,常量是在编译时被创建的,即使定义在函数内部也是如此,并且只能是布尔型
、数字型
(整数型、浮点型和复数)和字符串型
。
由于编译时的限制,定义常量的表达式必须为能被编译器求值的常量表达式。
声明格式:
const name [type] = value
例如:
const pi = 3.14159
type可以省略
和变量声明一样,可以批量声明多个常量:
const (
e = 2.7182818
pi = 3.1415926
)
所有常量的运算都可以在编译期完成,这样不仅可以减少运行时的工作,也方便其他代码的编译优化,当操作数是常量时,一些运行时的错误也可以在编译时被发现,例如整数除零、字符串索引越界、任何导致无效浮点数的操作等。
常量间的所有算术运算、逻辑运算和比较运算的结果也是常量,对常量的类型转换操作或以下函数调用都是返回常量结果:len、cap、real、imag、complex 和 unsafe.Sizeof。
因为它们的值是在编译期就确定的,因此常量可以是构成类型的一部分
如果是批量声明的常量,除了第一个外其它的常量右边的初始化表达式都可以省略,如果省略初始化表达式则表示使用前面常量的初始化表达式,对应的常量类型也是一样的。例如:
const (
a = 1
b
c = 2
d
)
fmt.Println(a, b, c, d) // "1 1 2 2"
常量声明可以使用 iota 常量生成器初始化,它用于生成一组以相似规则初始化的常量,但是不用每行都写一遍初始化表达式。
比如,定义星期日到星期六,从0-6
//在一个 const 声明语句中,在第一个声明的常量所在的行,iota 将会被置为 0,然后在每一个有常量声明的行加1
const (
Sunday = iota // 0
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
fmt.Println(Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday) // 0,1,2,3,4,5,6
指针(pointer)在Go语言中可以被拆分为两个核心概念:
受益于这样的约束和拆分,Go语言的指针类型变量即拥有指针高效访问的特点,又不会发生指针偏移,从而避免了非法修改关键性数据
的问题。同时,垃圾回收
也比较容易对不会发生偏移的指针进行检索和回收。
切片比原始指针具备更强大的特性,而且更为安全。
切片在发生越界时,运行时会报出宕机,并打出堆栈,而原始指针只会崩溃。
var a int = 10
当一个指针被定义后没有分配到任何变量
时,它的默认值为 nil
。
每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。
Go语言中使用在变量名前面添加&
操作符(前缀)来获取变量的内存地址(取地址操作),格式如下:
package main
import "fmt"
func main() {
var cat int = 1
var str string = "Hello,Golang!"
ptr := &cat //取出变量cat的地址,赋值给 ptr *int
fmt.Printf("%p,%p \n", &cat, &str) //打印出变量的内存地址
fmt.Println(*ptr) //取值
}
变量、指针和地址三者的关系是,每个变量都拥有地址,指针的值就是地址
当使用&
操作符对普通变量进行取地址操作并得到变量的指针后,可以对指针使用*
操作符,也就是指针取值
// 指针与变量
var room int = 10 // room房间 里面放的 变量10
var ptr = &room // 门牌号px 指针 0xc00000a0a8
fmt.Printf("%p\n", &room) // 变量的内存地址 0xc00000a0a8
fmt.Printf("%T, %p\n", ptr, ptr) // *int, 0xc00000a0a8
fmt.Println("指针地址",ptr) // 0xc00000a0a8
fmt.Println("指针地址代表的值", *ptr) // 10
取地址操作符&
和取值操作符*
是一对互补操作符,&
取出地址,*
根据地址取出地址指向的值
变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:
&
操作符,可以获得这个变量的指针变量。*
操作符,可以获得指针变量指向的原变量的值。通过指针不仅可以取值,也可以修改值。
package main
func main(){
// 利用指针修改值
var num = 10
modifyFromPoint(num)
fmt.Println("未使用指针,方法外",num) // 未使用指针,方法外 10
var num2 = 22
newModifyFromPoint(&num2) // 传入指针
fmt.Println("使用指针 方法外",num2) // 使用指针 方法外 10000
}
func modifyFromPoint(num int) {
// 未使用指针
num = 10000
fmt.Println("未使用指针,方法内:",num) //未使用指针,方法内 10000
}
func newModifyFromPoint(ptr *int) {
// 使用指针
*ptr = 1000 // 修改指针地址指向的值
fmt.Println("使用指针,方法内:",*ptr) // 使用指针 方法内 10000
}
Go语言还提供了另外一种方法来创建指针变量,格式如下:
new(类型) //返回 *类型的指针
ptr01 := new(string) // 等价于 var ptr01 *string = new(string)
*ptr01 = "Go天下第一"
fmt.Printf("%s", *ptr01)
new() 函数可以创建一个对应类型的指针,创建过程会分配内存,被创建的指针指向默认值。
获取命令行的输入信息
Go语言内置的 flag 包实现了对命令行参数的解析,flag 包使得开发命令行工具更为简单。
package main
// 导入系统包
import (
"flag"
"fmt"
)
// 定义命令行参数
var mode = flag.String("mode", "", "fast模式能让程序运行的更快")
func main() {
// 解析命令行参数
flag.Parse()
fmt.Println(*mode)
}
变量的生命周期指的是在程序运行期间变量有效存在的时间间隔。(跟Java中变量的生命周期基本一致)
变量的生命周期与变量的作用域有不可分割的联系:
全局变量:它的生命周期和整个程序的运行周期是一致的;
局部变量:它的生命周期则是动态的,从创建这个变量的声明语句开始,到这个变量不再被引用为止;
形式参数和函数返回值:它们都属于局部变量,在函数被调用的时候创建,函数调用结束后被销毁。
go的内存中应用了两种数据结构用于存放变量:
堆(heap):堆是用于存放进程执行中被动态分配的内存段。它的大小并不固定,可动态扩张或缩减。当进程调用 malloc 等函数分配内存时,新分配的内存就被动态加入到堆上(堆被扩张)。当利用 free 等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减);
栈(stack):栈又称堆栈, 用来存放程序暂时创建的局部变量,也就是我们函数的大括号{ }
中定义的局部变量。
栈是先进后出,往栈中放元素的过程,称为入栈,取元素的过程称为出栈。
栈可用于内存分配,栈的分配和回收速度非常快
在程序的编译阶段,编译器会根据实际情况自动选择
在栈
或者堆
上分配局部变量的存储空间,不论使用 var 还是 new 关键字声明变量都不会影响编译器的选择。
var global *int
func f() {
var x int
x = 1
global = &x
}
func g() {
y := new(int)
*y = 1
}
上述代码中,函数 f 里的变量 x 必须在栈上分配,因为它在函数退出后依然可以通过包一级的 global 变量找到,虽然它是在函数内部定义的。
用Go语言的术语说,这个局部变量 x 从函数 f 中逃逸了。
相反,当函数 g 返回时,变量 y 不再被使用,也就是说可以马上被回收的。因此,y 并没有从函数 g 中逃逸,编译器可以选择在栈上分配 *y 的存储空间,也可以选择在堆上分配,然后由Go语言的 GC(垃圾回收机制)回收这个变量的内存空间。
类型别名是 Go 1.9 版本添加的新功能,主要用于解决代码升级、迁移中存在的类型兼容性问题。
格式:
//TypeAlias 只是 Type 的别名,本质上 TypeAlias 与 Type 是同一个类型,就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人。
type TypeAlias = Type
还有一种是类型定义:
//定义Name为Type类型 ,定义之后 Name为一种新的类型
type Name Type
类型别名与类型定义表面上看只有一个等号的差异,那么它们之间实际的区别有哪些呢?
package main
import (
"fmt"
)
// 将NewInt定义为int类型
type NewInt int
// 将int取一个别名叫IntAlias
type IntAlias = int
func main() {
// 将a声明为NewInt类型
var a NewInt
// 查看a的类型名 main.NewInt
fmt.Printf("a type: %T\n", a)
// 将a2声明为IntAlias类型
var a2 IntAlias
// 查看a2的类型名 int
//IntAlias 类型只会在代码中存在,编译完成时,不会有 IntAlias 类型。
fmt.Printf("a2 type: %T\n", a2)
}
Go语言的注释主要分成两类,分别是单行注释和多行注释。
//
开头的单行注释;/*
开头,并以*/
结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。单行注释的格式如下所示
// 单行注释
多行注释的格式如下所示
/*
第一行注释
第二行注释
...
*/
每一个包都应该有相关注释,在使用 package 语句声明包名之前添加相应的注释,用来对包的功能及作用进行简要说明。
同时,在 package 语句之前的注释内容将被默认认为是这个包的文档说明。一个包可以分散在多个文件中,但是只需要对其中一个进行
注释说明即可。
关键字即是被Go语言赋予了特殊含义的单词,也可以称为保留字。
Go语言中的关键字一共有 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 |
之所以刻意地将Go语言中的关键字保持的这么少,是为了简化在编译过程中的代码解析。
和其它语言一样,关键字不能够作标识符使用。
标识符是指Go语言对各种变量、方法、函数等命名时使用的字符序列,标识符由若干个字母、下划线_
、和数字组成,且第一个字符必须是字母。下划线_
是一个特殊的标识符,称为空白标识符
标识符的命名需要遵守以下规则:
_
组成;var 1num int
是错误的;命名标识符时还需要注意以下几点:
在Go语言中还存在着一些特殊的标识符,叫做预定义标识符,如下表所示:
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 |
println | real | recover | string | true | uint | uint8 | uintptr |
预定义标识符一共有 36 个,主要包含Go语言中的基础数据类型和内置函数,这些预定义标识符也不可以当做标识符来使用。
所谓优先级,就是当多个运算符出现在同一个表达式中时,先执行哪个运算符。
Go语言有几十种运算符,被分成十几个级别,有的运算符优先级不同,有的运算符优先级相同,请看下表。
优先级 | 分类 | 运算符 | 结合性 |
---|---|---|---|
1 | 逗号运算符 | , | 从左到右 |
2 | 赋值运算符 | =、+=、-=、*=、/=、 %=、 >=、 <<=、&=、^=、|= | 从右到左 |
3 | 逻辑或 | || | 从左到右 |
4 | 逻辑与 | && | 从左到右 |
5 | 按位或 | | | 从左到右 |
6 | 按位异或 | ^ | 从左到右 |
7 | 按位与 | & | 从左到右 |
8 | 相等/不等 | ==、!= | 从左到右 |
9 | 关系运算符 | <、<=、>、>= | 从左到右 |
10 | 位移运算符 | <<、>> | 从左到右 |
11 | 加法/减法 | +、- | 从左到右 |
12 | 乘法/除法/取余 | *(乘号)、/、% | 从左到右 |
13 | 单目运算符 | !、*(指针)、& 、++、–、+(正号)、-(负号) | 从右到左 |
14 | 后缀运算符 | ( )、[ ]、-> | 从左到右 |
注意:优先级值越大,表示优先级越高。
一下子记住所有运算符的优先级并不容易,还好Go语言中大部分运算符的优先级和数学中是一样的,大家在以后的编程过程中也会逐渐熟悉起来。如果实在搞不清,可以加括号,就像下面这样:
d := a + (b * c)
括号的优先级是最高的,括号中的表达式会优先执行,这样各个运算符的执行顺序就一目了然了。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/m0_46571920/article/details/125590806
内容来源于网络,如有侵权,请联系作者删除!