GO 基础知识

文章目录
   一、常量和变量
   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
}

ContainsContainsAny的区别

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:将元素类型为 stringslice使用分隔符好拼接成一个字符串

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包)
一般来说,几乎所有类型都可以被转化成字符串,单反过来要把字符串转换成其他类型就不一定能够成功。前面有讲到runestring类型的转换。下面看下数字和字符串之间的转换。

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、位运算符

将数值转化为二进制数,按位操作

AB按位与(A&B)按位或按位异或(A^B)
00000
01011
11110
10011

比较难理解的应该是 按位异或
二元: 不同为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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值