文章目录
一、常量和变量
1、常量
const identifier [type] = value
2、变量
2.1 局部变量
identifier: = value <==> var identifier [type] = value
2.2 全局变量
var identifier [type] = value
2.3 匿名变量
" _"
二、基本数据类型
1、整型
int、uint
2、浮点
float32、float64
3、布尔型
bool
4、复数
complex32
complex64
5、字符串
string
6、字符串处理(strings 包)
6.1 包含判断
HasPrefix、HasSuffix、Contains、ContainsAny (返回值都是 bool )
6.2 索引
Index、LastIndex、IndexRune (返回值都是 int )
6.3 替换
Replace (返回一个新的 string)
6.4 统计
Count、utf8.RuneCountInString (返回值都是 int )
6.5 大小写转化
ToLower、ToUpper (返回一个新的 string)
6.6 修剪
Trim (返回一个新的 string)
6.7 分割
Splite (返回一个 slice (切片))
6.8 字符串转化成字符串切片
Fields (返回一个 []string 切片)
6.9 插入
Join
7、字符传与其他类型转换(strconv包)
7.1 数字转化成字符串
Itoa、FormatFloat、FormatBool
7.2 数值字符串转化成对应数值类型
Atoi、ParseFloat、ParseBool
7.3 自定义类型转化成字符串
Stringer
三、运算符
1、算术运算符
+、-、*、/、%、
2、关系运算符
>、<、==、!=、>=、<=
3、逻辑运算符
&&、||、!
4、位运算符
&、|、^、&^(按位擦除)
5、位移运算符
<<、>>
四、输入和输出
fmt包 : 输入和输出
1、输出
Print()、Printf()、Println()
常用格式化打印占位符
2、输入
Scanln()、Scanf()
bufio
五、基本数据类型扩展
1、强制类型转换
type_name(expression)
2、自定义类型
type name struct { }
3、类型别名
type
4、指针
存储了另一个变量的内存地址的变量
4.1 声明 var name *type
4.2 nil 指针
4.3 指针的指针 **ptr
4.4 指针数组 var ptr []*type 和 数组指针 *[]type
指针数组: 首先是个数组,数据类型是指针 []*T
数组指针: 首先是个指针,一个数组的地址 *[]T
4.5 指针函数和函数指针
指针函数 : 首先是个函数,返回值是指针
函数指针 : 首先是个指针,指向了一个函数
4.6 指针作为参数传递给函数
六、流程控制语句
1、条件语句
if、if-else、if-else if- else
2、选择语句
switch
3、循环语句
for、range
4、延迟语句
defer
5、标签语句
break、continue、goto
一、常量和变量
1、常量
在GO语言中,常量是指编译期间就已知,且在执行过程中不会改变的固定值。
一组常量中,如果某个常量没有初始值,默认和上一行一致。
iota 是特殊的常量,有递增效果,常用于实现枚举
每当定义一个常量组, iota 的初始值为 0,每当定义一个常量,就会累积加1,知道下一个常量组,才会清零。
const identifier[type] = value ,其中 type 可省略
批量定义常量
const a,b,c = 1,2,3
const (
x int = 1
y float64 = 2.0
z = true
j
)
fmt.Println(a,b,c,x,y,z,j) // 1,2,3,1,2.0,true,true
用于实现枚举
func enum() {
const(
a = iota
b
c = "hello"
d
)
const(
e = iota
)
fmt.Println(a,b,c,d,e) //0、1、hello、2 、0
}
常量没指定类型可以是任意类型。例如: a 可以是浮点也可以是整型。
2、变量
var identifier type
GO 与其他语言最大的不同在于: 声明变量时,变量类型放在变量名后面。
批量定义变量
var a,b,c = "string",1,true
var(
x,y int
str string
)
2.1 局部变量
func main() {
a := 1 //等价于 var a = 1
var b = 1
fmt.Println(a,b)
}
备注: a := 1
这种定义变量的方式只能用于函数体内。局部变量定义了必须使用,否则会报错。
2.2 全局变量
var z = true
func main() {
fmt.Println(z)
}
备注: 全局变量定义不限制位置,在函数外即可。允许声明但不使用。
2.3 匿名变量
GO 语言中,函数允许多个返回值。在日常处理中,我们可能会忽略部分返回值的情况,那么用"_"替代。
二、基本数据类型
1、整型
无符号 uint/uint8/uint16/uint32/uint64
有符号 int/int8/int16/int32/int64
其中: uint8 是 byte 的别称,int32 是rune 的别称。关于 byte 和 rune 后面会有介绍。
整型的运算与主流的编程语言并不差异。这里不做过多介绍。
不过需要注意的是: int 和 int8 算是两种不同的类型,不能直接赋值,也就是说将一个 int8 的值直接赋值给 int 类型的变量是错误的,必须经过强制转换。
2、浮点型
float32 精确到小数点后 7 位
float64 精确到小数点后 15 位
备注: GO语言中没有 float 这种类型。在运算中推荐使用 float64
3、布尔型
布尔型( bool )的值只可以是 ture 或者 false
支持一元运算符 : !
支持二元运算符 : && 、|| 、==
格式化输出: %t
4、复数
规定 i = 根号-1,也就是 i^2 = 1
复数 a + bi 的模 = a^2 + b ^2 的算术平方根,其中 a 为实部位,b 为虚部
复数支持常规数据运算,实部和虚部都是浮点型
在运算中推荐使用complex64
操作复数的方法在cmath
包中
func complexValue() {
a := 3.2 + 5i
b := complex(3.2,5) // b := 3.2 + 5i
fmt.Printf("复数 a 的实部 = %f, 复数 b 的实部 = %f\n",real(a),real(b))
fmt.Printf("复数 a 的虚部 = %f, 复数 b 的虚部 = %f\n",imag(a),imag(b))
fmt.Printf("复数 a 的模 = %f",cmplx.Abs(a))
}
著名欧拉公式 :e^(i+pi) +1 = 0
fmt.Println(cmplx.Pow(math.E,1i*math.Pi)+1)
fmt.Println(cmplx.Exp(1i*math.Pi)+1)
fmt.Printf("%.3f\n",cmplx.Exp(1i*math.Pi)+1)
5、字符串
在 GO 语言中,字符串是一个不可变的 UTF-8 字符序列。一个 ASCII 码占用一个字节,其他字符根据需要占用 2 ~ 4 字节。字符串是一个定长的字节数组(访问字符串中的字节类似访问数组中的元素方式)。GO 语言默认就是采用 UTF-8 编码格式进行文件存储。
5.1 定义一个字符串
var test string = "abc.txt"
str := "abc 中国"
//字符串是定长的字节数组
s1 := []byte{65,66,67,68,69}
fmt.Println(string(s1)) //ABCDE
5.2 获取字符串字节数目和字符数目
str := "cherish中国"
r := []rune(str)
fmt.Printf("字符串 str 的字节数 = %d ,字符数 = %d \n",len(str),len(r)) // 13、9
5.3 访问字符串中某个字节、某个字符
str := "aA 你 2"
for index := 0; index < len(str); index++{
fmt.Println(str[index])
}
str[index]返回第 index 个字节的字节值, 0 <= index < len[str]
输出:
97 //a
65 //A
32 //空格
228 //你
189 //你
160 //你
32 //空格
50 //2
//遍历字符串一般使用 range 方式
str := "cherish中国"
for index,value := range str{
fmt.Printf("index = %d ,value = %c \n",index,value)
}
输出
index = 0 ,value = c
index = 1 ,value = h
index = 2 ,value = e
index = 3 ,value = r
index = 4 ,value = i
index = 5 ,value = s
index = 6 ,value = h
index = 7 ,value = 中
index = 10 ,value = 国
5.4 解释字符串和非解释字符串
解释字符串: 使用 ""
括起来,不能换行,并且能解析转义字符
非解释字符串 : 使用 反单引号 括起来,支持换行,反单引号内所有内容直接输出
注意下 “a” 和 ‘a’的区别 : “” 是字符串,’’ 是byte
在 go 中,如果需要打印带有 “xxx” 类似的字符串,必须""转义下。例如 :
fmt.Println("\"hello go\"") // "hello go"
在格式化输出的时候,%
是一种占位符标识,配合不同字母表示占位不同类型:
%d
表示整数
%%
表示 % 字面量
%f
表示小数
%c
表示一个Unicode字符
%s
表示字符串
%t
表示布尔类型
%T
输出值的类型
%v
原样输出
%x
小写的16进制数值
%X
大写的16进制数值
%p
指针
5.5 字符串拼接
字符串可以拼接,用 " + " 拼接字符串,但原字符串不会改变
str := "你好"
t := str
str += "golang"
fmt.Printf("str = %s,t = %s",str,t) //str = 你好golang,t = 你好
5.6 字符串之间的比较
在 GO 语言中,字符串是可以使用 “==” 和 “<” 等符号进行比较的,通过比较逐字节的编码获得结果。
s := "你"
t := "好"
if s < t {
fmt.Println(s[0],t[0]) //228 229
fmt.Println(s[1],t[1]) //189 165
fmt.Println(s[2],t[2]) //160 189
}
a := "a"
b := "b"
if a < b {
fmt.Println(a[0],"小于",b[0]) //97 小于 98
}
test := "你好"
test1 := "你好"
if test == test1{
fmt.Println("两字符串相等")
}
5.7 字符串修改
在 GO 语言中,字符串内容不能修改,也就不能使用 s[i] 这种方式修改字符串中的 UTF-8 编码。如果确实要修改,那么可以将字符串的内容复制到另一个可写变量中,然后再进行修改。
如果要对字符串中的字节进行修改,则转换成 []byte 格式
如果要对字符串中的字符进行修改,则转换成 []rune 格式
转换过程中,会自动复制数据。
s := "Hello Word!"
b := []byte(s) //转换为 []byte,自动复制数据
b[5] = ',' // 修改 []byte
fmt.Printf("修改字符串中的字节: s = %s \n b = %s\n",s,b)
//输出
修改字符串中的字节: s = Hello Word!
b = Hello,Word!
s1 := "Hello 中国!"
r := []rune(s1) //转化成 []run,自动复制数据
r[6] = '世' //修改 []run
r[7] = '界' //修改 []run
fmt.Printf("修改字符串中的字符: s = %s \n b = %s\n",s1,string(r))
//转化成字符串,又一次复制数据
//输出
修改字符串中的字符: s = Hello 中国!
r = Hello 世界!
备注 : 如果字符串是纯 ASCII 字符串,两种是等价的。
5.8 字符串截取
在日常开发中,我们经常会遇到字符串截取的需求。如果优雅在进行字符串截取呢,这里涉及到部分 slice 内容。建议初学者后面学到 slice 再往这里看。
** 5.8.1 字节截取**
test := "你好hello word"
substr := test[3:7]
fmt.Println(substr) // 好h => 截取 区间 [3,7) 之前的字节
5.8.2 字符截取
str := "你好hello word"
r := []rune(str)
fmt.Println(string(r[1:5])) //好hel => 截取区间 [1,5)之间的字符
fmt.Println(string(r[1:])) //好hello word => 截取 index = 1 后面的全部字符
fmt.Println(string(r[:2])) //你好 =>截取 [0,2) 前的全部字符,不包含 index = 2
6、字符串处理(strings包)
对于基本类型来说,字符串所需要执行的操作比较复杂,所以一般语言都会额外封装一些方法用于处理字符串,GO 语言标准库中也存在这样一个名为 strings
的库。
6.1 包含判断
HasPrefix
(包含前缀)
HasSuffix
(包含后缀)
Contains
(包含子串)
ContainsAny
(匹配更广泛的内容,甚至可以匹配 Unicode 字符)
func stringsContain(){
str := "This is an example of a string"
fmt.Printf("Does the string \"%s\" have prefix %s ?\n",str,"Th") //true
fmt.Printf("%t\n",strings.HasPrefix(str,"Th"))
fmt.Printf("Does the string \"%s\" have suffix %s ?\n",str,"string")
fmt.Printf("%t\n",strings.HasSuffix(str,"string")) //true
fmt.Printf("Does the string \"%s\" have contain %s ?\n",str,"example")
fmt.Printf("%t\n",strings.Contains(str,"string")) //true
}
Contains
和 ContainsAny
的区别
func stringContainsAndContainsAny() {
fmt.Println(strings.Contains("team","i")) //false
fmt.Println(strings.Contains("team"," a & o")) //false
fmt.Println(strings.Contains("team","")) //true
fmt.Println(strings.Contains("","")) //true
fmt.Println()
fmt.Println(strings.ContainsAny("team","i")) //false
fmt.Println(strings.ContainsAny("team"," a & o")) //true
fmt.Println(strings.ContainsAny("team","")) //false
fmt.Println(strings.ContainsAny("","")) //false
}
备注 :
Contains
底层实现是Index
,精确查找指定子串是否存在
ContainsAny
底层实现是 IndexAny
,第二个参数且不为空中只要有一个字符出现在目标字符串,即为 true
;为空,即为 false。
6.2 索引
Index
:指定字符/串的第一次出现的第一个字符的索引值,如果不存在相应字符串则返回-1
LastIndex
:指定字符串最后一次出现位置的第一个字符索引,-1表示不包含特定字符串
IndexRune
:如果是非 ASCII 编码的字符,用此函数对字符进行定位。也是第一次出现第一个字符的索引值。不存在返回-1
func searchStrIndex() {
indexstr := "你好hello goland好"
fmt.Println(strings.Index(indexstr,"好")) // => 3,str[3],中文 "你" 占 3个字节。
//和 Index 一个意思,都是检查第一次出现
fmt.Println(strings.IndexRune(indexstr,/*[]rune(indexstr)[1]*/'好')) // =>3
// 和 Index 相反,检查最后一次出现
lastIndexStr := "hello word"
fmt.Println(strings.LastIndex(lastIndexStr,"o")) // =>7
}
6.3 替换
func Replace(s, old, new string, n int) string
第一个参数 : 原字符串
第二个参数 : 源字符串中需要被替换的字符串
第三个参数:替换内容
第4个参数:n 表示匹配到第几个 old,如果 n = -1,表示匹配所有
func stringReplace() {
str := "你好世界,这个世界真好。"
var newStr string = strings.Replace(str,"世界","地球",1)
fmt.Println(newStr) //你好地球,这个世界真好。
}
6.4 统计
Count
: 统计指定字符串出现的频数
utf8.RuneCountInString
: 统计指定字符串的字符数量
func stringStatistics() {
str := "Golang is cool,right?"
var many string = "o"
fmt.Printf("指定字符 \"%s\" 在 字符串 \"%s\"中 出现的频数为:%d \n",many,str,strings.Count(str,many)) // 3
fmt.Printf("字符串 \"%s\"的字符数为 %d \n",str,len([]rune(str))) //21
fmt.Printf("字符串 \"%s\"的字符数为 %d \n",str,utf8.RuneCountInString(str)) //21
}
6.5 大小写转化
ToLower
: 将输入字符串转化成小写输出
ToUpper
:将输入字符串转化成大写输出
func stringToUpperOrToLower() {
str := "Hello Golang"
var lower string = strings.ToLower(str)
var upper string = strings.ToUpper(str)
fmt.Printf("\"%s\" lower is \"%s\" and upper is \"%s\"",str,lower,upper)
}
6.6 修剪
Trim
这个函数修剪的是字符串开头或者结尾的字符,也就是不匹配中间的字符,而且 Trim
函数第二个参数虽然类型是 stirng
,但实际上替换时是把字符串转换成 rune
后来操作的。
func stringTrim() {
str := "今天天气真好"
fmt.Println(strings.Trim(str,"今")) //天天气真好
fmt.Println(strings.Trim(str,"天")) //今天天气真好
fmt.Println(strings.Trim(str,"今天")) //气真好
}
第一句的确裁剪了"今"字,但是第二句的"天"并非字符串首尾,所以不会裁剪,重点在于第三句,可以看到后面的"天"全部被裁剪了,这是因为Trim
函数把第二个参数转化成了rune
类型,进行了逐一匹配–换句话说,实际上"今天"是被分为两个字符分别匹配裁剪的,所以只返回了"气真好"。
6.7 分割
Splite
: 返回一个切片(slice)
func stringSplit() {
fmt.Println(strings.Split("a,b,c",",")) //[a b c]
fmt.Printf("%q\n",strings.Split("a,b,c",",")) //["a" "b" "c"]
fmt.Printf("%q\n",strings.Split("a b c"," ")) //["a" "b" "c"]
fmt.Printf("%q\n",strings.Split("a b c "," ")) //["a" "b" "c" ""]
}
6.8 ~ 6.9 字符串转化成字符串切片(Fields)、字符串切片拼接成字符串(Join)
Fields
:以连续的空白字符为分隔符,将 string 切分成多个子串,结果中不包含空白字符本身,空白字符有:\t, \n, \v, \f, \r, ' '
Join
:将元素类型为 string
的slice
使用分隔符好拼接成一个字符串
func stringInsert() {
str := "Hello 世\n界!\tHe\vl\flo!"
var slice []string = strings.Fields(str)
fmt.Printf("%q\n",slice)
var result string = strings.Join(slice,"")
fmt.Println(result)
}
输出:
["Hello" "世" "界!" "He" "l" "lo!"]
Hello世界!Hello!
7、字符串和其他类型转换(strconv包)
一般来说,几乎所有类型都可以被转化成字符串,单反过来要把字符串转换成其他类型就不一定能够成功。前面有讲到rune
和 string
类型的转换。下面看下数字和字符串之间的转换。
7.1 数字转化成字符串
Itoa
整型、FormatFloat
浮点型、FormatBool
布尔型 转化成字符串
//1.整型数字到字符串
convint := strconv.Itoa(10)
//convint := strconv.FormatInt(10,10) 第二个参数是十进制的意思
fmt.Println(convint,reflect.TypeOf(convint)) // 10 string
//2.浮点性数字到字符串
convfloat := strconv.FormatFloat(3.23,'g',3,64)
fmt.Println(convfloat,reflect.TypeOf(convfloat)) //3.23 string
//3.布尔型到字符串
convbool := strconv.FormatBool(true)
fmt.Println(convbool,reflect.TypeOf(convbool)) // true string
7.2 字符串转化成数字
Atoi
整型、ParseFloat
浮点型、ParseBool
布尔型 字符串转化成对应基本数据类型
//4.整型字符串到整型
if result,err := strconv.Atoi("10");err != nil{
fmt.Println(err)
}else {
fmt.Println(result,reflect.TypeOf(result)) //10 int
}
或者
value ,error := strconv.ParseInt(s1,10,64)
if error == nil {
fmt.Println(value)
}
//5.布尔类型字符串到布尔类型
if result,err := strconv.ParseBool("1");err != nil{
fmt.Println(err)
}else {
fmt.Println(result,reflect.TypeOf(result)) //true bool
}
//6.浮点类型字符串到浮点类型
if result,err := strconv.ParseFloat("3.24",64);err != nil{
fmt.Println(err)
}else {
fmt.Println(result,reflect.TypeOf(result)) //3.24 float64
}
备注 : 上面的reflect.TypeOf
是个反射api。返回对应数据类型。
7.3 自定义类型转化成字符串
实现 Stringer
接口,实现 String() string
方法 即可.
type Student struct {
Name string
}
func (s Student) String() string {
return "name is " + s.Name
}
func cusTypeConvString() {
jack := new(Student)
jack.Name = "jack"
fmt.Println(jack.String()) // name is jack
}
三、运算符
1、算术运算符
+、-、*、/、%、++、–
注意 : 在 go 中, ++ 和 --是语句,不是表达式
关于 立方、开根号等在 math
包中
var a = 1
a++
a--
//++a 这样是错误的
//--a 这样也是错误的
b := n++ //这样也是错误的
2、关系运算符
== 、!=、 >、<、>=、<=
3、逻辑运算符
|| (短路或) : A || B =>只要 A 或 B其中一个为真,结果为真,A 和 B 不用全部参与运算
&&(短路与) :A && B => 只要 A 或 B其中一个为假,结果为假,A 和 B 不用全部参与运算
! (非) : !A => A为true,结果为 false
4、位运算符
将数值转化为二进制数,按位操作
A | B | 按位与(A&B) | 按位或 | 按位异或(A^B) |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
比较难理解的应该是 按位异或:
二元: 不同为1,相同为0
一元 :按位取反
GO 语言特有的 按位清除 : &^
例如: A &^ B ,针对 B 上的每个数值,如果为 0 ,取 A 对应上的数值,如果为 1,结果位取 0 。
package main
import "fmt"
func main() {
a := 60
b := 13
/*
a: 60 0011 1100
b: 13 0000 1101
& 0000 1100 => 1*2^3+1*2^2 = 12
| 0011 1101 => 1*2^5+1*2^4+1*2^3+1*2^2+0*2^1+1*2^0 = 61
^ 0011 0001 => 1*2^5+1*2^4+1*2^0 = 49
&^ 0011 0000 => 1*2^5+1*2^4 = 48
*/
fmt.Printf("%d---%b \n", a, a)
fmt.Printf("%d---%b \n", b, b)
fmt.Printf("%d & %d = %d \n",a,b,a&b)
fmt.Printf("%d | %d = %d \n",a,b,a|b)
fmt.Printf("%d ^ %d = %d \n",a,b,a^b)
fmt.Printf("%d &^ %d = %d \n",a,b,a&^b)
}
//输出结果:( %b为十进制数的二进制表现 )
60---111100
13---1101
60 & 13 = 12
60 | 13 = 61
60 ^ 13 = 49
60 &^ 13 = 48
5、位移运算符
<< : 按位左移,a << b, 将 a 转化成二进制数,向左移动 b 位
<< : 按位右移,a >> b, 将 a 转化成二进制数,向右移动 b 位
package main
import "fmt"
func main() {
c := 8
/*
c:8 0000 1000
<<2 000010 0000 => 1*2^5 = 32
>>2 0000 0010 => 1*2^1 = 2
*/
fmt.Printf("%d---%b \n", c, c)
fmt.Printf("%d << 2 = %d\n",c,c<<2)
fmt.Printf("%d >> 2 = %d\n",c,c>>2)
}
//输出结果:
8---1000
8 << 2 = 32
8 >> 2 = 2
四、输入和输出
fmt
包 : 输入和输出
1、输出:(打印到控制台)
Print() //打印
Printf() //格式化打印
Println() //输出后打印
格式化打印占位符
%% %
%v 原样输出
%T 打印类型
%t 布尔类型
%s 字符串
%d 10进制数
%b 2进制数
%o 8进制
%x %X 16进制
%x: 0-9,a-f
%X:0-9,A-F
%c 打印字符
%p 打印地址
2、输出(键盘输入)
Scanln() //读取键盘输入,通过操作地址,赋值给对应变量,阻塞式
Scanf() //按照格式读取键盘输入,通过操作地址,赋值给对应变量,阻塞式
package main
import "fmt"
func main() {
fmt.Println("请输入一个整数,一个小数")
var x int
var y float64
fmt.Scanln(&x,&y)
fmt.Printf("x = %d,y = %f\n",x,y)
}
或者通过 bufio
的包也可实现输入(这个包主要用于文件读写)
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
fmt.Println("打印一个字符串")
reader := bufio.NewReader(os.Stdin)
str,_ := reader.ReadString('\n') //表示读取到"\n"就结束
fmt.Println(str)
}
五、基本数据类型扩展
1 、强制类型转换
类型转换用于将一种数据类型的变量转化成良一中类型的变量。GO 语言的类型转化基本格式: type_name(expression)
在做强制类型转换时,需要注意数据长度被截短而发生的数据精度丢失(比如浮点转化成整数) 和 值溢出 (值超过转换的目标类型的值范围时) 问题。
func typeExpression() {
sum := 11
count := 3
mean := float32(sum) / float32(count)
mean1 := sum / count
fmt.Printf("mean = %.2f \n",mean) //mean = 3.67
fmt.Printf("mean1 = %d \n",mean1) //mean1 = 3
}
2、 自定义类型
在 GO 语言中,自定义类型实际上与结构体有关。这里暂不介绍关于结构体过多内容的说明。上面已经有一个 Student
结构体。
type name struct { //TO DO }
3、 类型别名
type
给已知的数据类型取另外一个名字。
func typeAlternativeName() {
type 字符串 string
var b 字符串
b = "这是中文。"
fmt.Println(b) // 这是中文。
a := "这也是一个中文"
//fmt.Println(a+b)
fmt.Println(string(b)+a) //这是中文。这也是一个中文
}
注意 : 上面例子中,注意注释的那一句,在编译器中这种写法是错误的,因为类型别名与原有类型是两种类型,不能直接操作,需要进行类型转换。
4、指针
4.1 声明
指针变量都是指向一个内存位置,每个内存位置都有其定义的地址,可以使用 &
运算符来访问它,这个运算符表示内存中的地址。
指针是一个变量,其值是另外一个变量的地址,即存储器位置的直接地址。类似变量和常量一样,必须先声明一个指针,然后才能使用它来存储任何变量地址。指针的变量声明一般格式为: var name *type
所有指针的值的实际数据类型都是相同的,他表示内存地址的长十六进制数。
使用指针基本上是三个步骤:
1. 定义一个指针变量
2. 将一个变量的地址赋值给一个指针
3. 最后访问指针变量中可用地址的值
GO 语言中通过使用一元运算符*
来取得指向指针存储的内存地址所对应的值(指针的格式化标识符为 %p)。GO语言指针不支持指针运算,不支持 -> 运算符。
func pointer() {
a := 20
pointer := &a
fmt.Printf("a 的地址: %x \n",&a) //a 的地址: c0000b4008
fmt.Printf("pointer 的地址: %x \n",pointer) //pointer 的地址: c0000b4008
fmt.Printf("pointer 的值: %d\n",*pointer) //pointer 的值: 20
}
4.2、 nil 指针
GO 语言编译器为指针变量分配一个 nil
值,以防指针没有确切的地址分配,这是在变量声明的时候完成的。指定为 nil
值 的指针称为 nil 指针,nil 指针 是在几个标准库中定义的值为 0 的常量。在大多数操作系统中,程序不允许访问地址 0 处的内存,因为改内存是由操作系统保留的。然而,存储器地址 0 具有特殊意义,他表示指针不打算指向可访问的存储器的位置。如果指针包含 nil 值,则假设他不指向任何对象。要检查是否为 nil 指针,可以使用 if 语句
if(ptr != nil)
if(ptr == nil)
对于一个空指针的反向引用是不合法的,并且会使程序 crash
var ptr *int = nil
*ptr = 10
4.3、 指针的指针
直接上两个例子说明下就好理解
func pointerOfThePointer() {
var a *int
ap := &a
if a == nil {
fmt.Println("a 为 nil 指针")
}
fmt.Printf("a --> nil : %x\n",a) //0
fmt.Printf("ap --> a : %x \n",ap)
fmt.Printf("ap -> a --> nil(指针 ap 指向的指针 a 的内存地址):%x \n",*ap) //0
fmt.Printf("&ap --> ap(表示 ap 在内存中的地址):%x\n",&ap)
}
func doublePointer() {
a := 10
ap := &a
app := &ap
fmt.Printf("a = %d \n",a) //10
fmt.Printf("ap = %x \n",ap) //10在存储器位置上直接地址
fmt.Printf("*ap = %d \n",*ap) // 10
fmt.Printf("app = %x \n",app) // 变量 ap 在存储器位置上的直接地址
fmt.Printf("*app = %x \n",*app) // 10 在存储器上的直接地址
fmt.Printf("**ap = %d \n",**app) //10
}
输出如下:
a = 10
ap = c000016050
*ap = 10
app = c00000e028
*app = c000016050
**ap = 10
4.4 指针数组和数组指针
指针数组: 首先是一个数组,存放的数据类型是指针
数组指针: 首先是一个指针,一个数组的地址
[4]*int => 数组,长度为 4 的整形指针数组
*[4]int => 指针,长度为 4 的整形数组的指针
*[4]*int => 指针,长度为 4 的整形指针数组的指针
**[4]int => 指针,存储 4 个整形数据的数组指针的指针
**[5]*int => 指针,存储 4 个整形的指针地址数组,指针的指针
虽然我们还未介绍到 GO 数组,但是并不妨碍理解指针数组的概念。
func pointerArrays() {
a := [3]int{10,20,30}
var ptr [3]*int
for index := 0; index < 3;index++ {
ptr[index] = &a[index]
fmt.Printf("a[%d]的地址:%x\n",index,ptr[index])
}
for index := 0 ;index < 3;index++ {
fmt.Printf("a[%d]的值为:%d\n",index,(*ptr)[index])
}
}
输出结果:
a[0]的地址:c00001c0e0
a[1]的地址:c00001c0e8
a[2]的地址:c00001c0f0
a[0]的值为:10
a[1]的值为:20
a[2]的值为:30
4.5 指针函数和函数指针
函数指针 : 首先是个指针,指向了一个函数
指针函数 : 首先是个函数,返回值类型是指针
在 GO 语言中,函数名本身就是一个函数指针
package main
import "fmt"
func main(){
a := funptr
a()
fmt.Println(a)
}
func funptr() {
fmt.Println("Hello World")
}
/**输出结果
Hello World
0x109dff0
*/
指针函数:是个函数,返回值是指针类型
package main
import "fmt"
func main(){
a := prtfunc()
fmt.Printf("%T\n",a)
fmt.Print(*a)
}
func prtfunc()*[4]int{
arr := [4]int{1,2,3,4}
return &arr
}
/**输出结果
*[4]int
[1 2 3 4]
*/
4.6、指针作为参数传递给函数
GO 语言允许传递指针到函数中,只需要将函数参数声明为指针类型。
说白了,其实就是引用传递。
func swap(a *int,b *int) {
temp := *a
*a = *b
*b = temp
}
func paramsPointer() {
a := 100
b := 200
swap(&a,&b)
fmt.Printf("a = %d,b = %d \n",a,b) //a = 200,b = 100
}
六、流程控制语句
1、条件语句
if、if-else、if-else if- else
跟大多数语言一样,我们要说的就是 GO 语言特有的点: if 语句可以有一个子语句,子语句只能有一个表达式。if 后面的括号可以省略。
func conditionStatement() {
if a := 10; a < 20 {
fmt.Println("a 小于 20")
}else{
fmt.Printf("%d 大于或等于 20 \n",a)
}
if a,b := 10,20; a< b {
fmt.Println("a < b")
}else {
fmt.Println("a >= b")
}
/* 编译错误。初始化子语句只能有一个表达式
if a := 10; b := 10; a < b {
fmt.Println("a < b")
}else{
fmt.Println("a >= b")
}*/
}
2、选择语句
switch
跟大多数语言一样,这里还是讲特殊点: break可省略,fallthrough 关键字可以把当前case控制权移交给下一个case语句判断。switch同样和if一样拥有初始化子语句。
表达式 switch : switch 后面可以没有表达式,case 后可以跟表达式。
注意: switch 可以作用在其他类型上,case后面的数值必须跟switch作用的变量类型保持一致,省略 switch 后面的变量,相当于直接作用于 true 上。
var x interface{}
func switchStatement() {
//1.表达式switch: switch后面没有语句;case可以多个语句,语句之间用","分割,跟 fallthrough 意思一致
marks := 95
switch {
case marks >= 90:
fmt.Println("优秀")
case marks >= 80,marks >= 70:
fmt.Println("良好")
default:
fmt.Println("一般")
}
//2.类型switch: 跟主流编程语言一致。但是 break 可以省略不写。
x = 10
switch dataType := x.(type){
case int:
fmt.Printf("int 类型 %T \n",dataType)
case float64:
fmt.Printf("flaot64 类型 %T \n",dataType)
case bool:
fmt.Printf("bool 类型 %T \n",dataType)
case string:
fmt.Printf("string 类型 %T \n",dataType)
case nil:
fmt.Printf("nil 类型 %T \n",dataType)
default:
fmt.Println("未知类型")
}
}
3、循环语句
for、range
for 语句跟主流编程语言保持一致,只不过 for 后面的语句括号省略
这里主要是 range: 这货非常强大,其作用类似于迭代器,用于轮询数组或者切片的每一个元素,也可以用于轮询字符串中的每一个字符,以及字典中的每一个键值对,甚至还可以持续读取一个通道类型值中的元素
func rangeStatement() {
//1. 字符串
fmt.Println("range 字符串")
str := "I Love 中国"
for index,char := range str{
fmt.Printf("str[%d] = %c\n",index,char)
}
fmt.Println()
fmt.Println("range 数组或切片")
//2.数组或切片
arrays := [5]int{1,2,3,4,5}
for index,value := range arrays{
fmt.Printf("arrays[%d] = %d\n",index,value)
}
fmt.Println()
fmt.Println("range 字典")
//3.字典(映射)
dictionary := map[string]string{"name":"jack","country":"china"}
for key,value := range dictionary{
fmt.Printf("dictionray[%s] = %s \n",key,value)
}
}
输出结果
range 字符串
str[0] = I
str[1] =
str[2] = L
str[3] = o
str[4] = v
str[5] = e
str[6] =
str[7] = 中
str[10] = 国
range 数组或切片
arrays[0] = 1
arrays[1] = 2
arrays[2] = 3
arrays[3] = 4
arrays[4] = 5
range 字典
dictionray[name] = jack
dictionray[country] = china
备注: GO 没有 三目运算符,没有 do-while 。
4、延迟语句
defer: 用于延迟调用指定函数,defer
关键字只能出现在函数内部
两大特点
4.1 只有当 defer
语句全部执行,defer
所在函数才算真正结束
4.2 当函数中 有 defer
语句时,需要等待所有 defer
语句执行完毕,才会执行 return
语句
defer 一般用于回收资源,清理收尾等工作
defer 其实是一个栈,遵循先入后出(后进先出),简单来说,当一个函数内部出现多个 defer
语句时,最后面的 defer
语句最先执行
func deferStatement() {
for index := 0;index < 5;index++ {
defer printStatement(index)
}
}
func printStatement(i int) {
fmt.Println(i)
}
输出结果:
4
3
2
1
0
5、标签语句
GO 语言中,可以给 for、switch 等流程控制代码打上一个标签,配置标签标识符可以方便跳转到某一个地方继续执行,有助于提高编程效率。标签名字区分大小写,为了提高阅读性,建议标签使用大写字母和数字组合。标签可以标记任何语句,未使用的标签会引发错误。
break
(跳出for循环)、continue
(仅能用于for循环)、goto
(只能在同一个函数中跳转)
下面列举三个方法简单解释下三个标签语句
5.1 break
func breakTagStatement() {
LOOP1:
for {
x := 1
switch {
case x > 0:
fmt.Println("A")
break LOOP1
case x == 1:
fmt.Println("B")
default:
fmt.Println("C")
}
}
}
输出结果:
A
5.2 contiune
func contiuneTagStatement() {
LOOP1:
for index := 0; index <= 5;index++ {
switch {
case index > 0:
fmt.Println("A")
continue LOOP1
case index == 1:
fmt.Println("B")
default:
fmt.Println("C")
}
fmt.Printf("index = %d \n",index)
}
}
输出结果:
C
index = 0
A
A
A
A
A
5.3 goto
func gotoTagStatement() {
var i int
for {
fmt.Println(i)
i++
if i > 2 {
goto BREAK
}
}
BREAK:
fmt.Println("break")
}
输出结果:
0
1
2
break