Go语言基础学习笔记

Go语言基础学习笔记—写在前面

作为一名在体制内安逸了很久的”伪”程序员,大学学的本科学的环境工程,研究生是软件工程,一毕业就考了编制,一直从事网络管理(摸鱼)工作,哪里坏了找外包公司就行。没有程序公司实习经历,学的很多都已经还给了老师。今年年底的时候,领导问我能不能把内网开发的好一点,明年年初申请个课题啥的,有一点小经费。于是我开始摸索Web编程,准备使用Go语言实现这个功能,业余时间在B站上跟着一个视频看了有50多集把,整理了一点小的笔记,大部分是上课的那个老师写的,我把它都打字录入下来了,与大家共享一下。
PS:我数据结构,组原啥的,已经全忘了,计算机网络还知道一点。

因为是照着视频里老师的打的,我都不知道写原创还是转载,包括代码也是我手敲的
点个原创把(是不是有点过分了,哈哈哈哈哈)

视频是这个:https://www.bilibili.com/video/BV1b4411h78s
讲了这么多废话,现在开始。

Lesson 1 Hello World

第一节就是Hello World啦。
介绍了基本的组成,有包哦,和c和java都好相似呢,偏向c一点。

//示例代码
package main

import "fmt"

func main() {
	fmt.Println("HelloWorld!")
}

Lesson 2 常量、变量与运算符

2.1变量的声明

1、初始化变量名的标准格式

var 变量名 类型 = 表达式

2、初始化变量编译器自动推断类型格式

var 变量名 = 表达式

3、初始化变量的简短声明格式(短变量声明格式)

变量名 := 表达式

使用:=赋值操作符,:=可以高效地创建一个新的变量,称之为初始化声明,声明语句省略了var关键,变量类型将由编译器自动推断,这是声明变量的首选形式,但是它只能被用在函数体内,而不可以用于全局变量的声明与赋值,该变量名必须市没有定义过的变量,若定义过,将发生编译错误,在多个段变量声明和赋值中,至少有一个新声明的变量出现在左侧中,那么即使有其它变量名可能是重复声明的,编译器也不会报错。

//示例代码
package main

import "fmt"

var m int = 19

func main() {
	var (
		a int
		b string
		c float32
		d []int
		e int32 = 100
		f [3]int
		g bool
		h func() string
		l = 200.234
	)
	m := "19"
	n := "190"
	m, _, q := "a", "b", 10
	//Go语言的函数可以返回多个值,而且事实上我们并不是对所有的返回值都用的上。
	//那么就可以使用匿名变量,用“-”下划线替换即可。匿名变量不占用命名空间,不会分配内存。
	fmt.Printf("%T ,%v\n", a, a)
	fmt.Printf("%T ,%q\n", b, b)
	fmt.Printf("%T ,%v\n", c, c)
	fmt.Printf("%T ,%v\n", d, d)
	fmt.Printf("%T ,%v\n", e, e)
	fmt.Printf("%T ,%v\n", f, f)
	fmt.Printf("%T ,%v\n", g, g)
	fmt.Printf("%T ,%v\n", h, h)
	fmt.Printf("%T ,%v\n", l, l)
	fmt.Printf("%T ,%v\n", m, m)
	fmt.Printf("%T ,%v\n", n, n)
	fmt.Printf("%T ,%v\n", q, q)

	x := 10
	y := 20
	z := 30
	fmt.Println(x, " ", y, " ", z)
	x, y, z = y, z, x
	fmt.Println(x, " ", y, " ", z)

	var abc uint8 = 2300
	fmt.Println(abc)
}

2.2数据类型与打印的格式化

(一)通用

%v 值的默认格式表示
%+v 类似%v,但是输出结构体时会添加字段名
%#v 值的Go语法表示 %T 值的类型的Go语法表示

(二)布尔值

%t 单词true或者false

(三)整数

%b 表示为二进制 binary
%c 该值对应unicode码值 char
%8d 表示该整型长度是8,不足8则在数值前补空格。如果超出8,则以实际为准
%08d 表示数字长度是8,不足8位的,在数字前补0.如果超出8,则以实际为准。 %o 表示为八进制 octal
%q 该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示。 quotation %x 表示为十六进制,使用a-f hex
%X 表示为十六进制,使用A-F %U 表示Unicode格式:U+1234,等价于”U+%04X“ unicode

(四)浮点数与复数的两个部分

%b 无小数部分、二进制指数的科学计数法,如-123456p-78;参见strconv。FormatFloat
%e(=%。6e)有6位小鼠部分的科学计数法,如-1234.456e+78
%E 科学计数法,如-1234.456E+78
%f (=%.6f)有6位小数的部分,如123.456123 float %F 等价于%f
%g 根据实际情况采用%e或%f格式(以获得更简洁、准确的输出)
%G 根据实际情况采用%E或%F格式(以获得更简洁、准确的输出)

(五)字符串和byte

%s 直接输出字符串或者[]byte string
%q 该值对应的双引号括起来的Go语法字符串字面值,必要时会采用安全的转义表示
%x 每个字节用两个字符十六进制数表示(使用a-f)
%X 每个字节用两个字符十六进制数表示(使用A-F)

(六)指针

%p表示为16进制,并加上前导的0x pointer
没有%u。整数如果是无符号类型自然输出也是无符号的。
类似的,也没有必要指定操作数的尺寸(int8,int64)
宽度通过一个紧跟在百分号后面的十六进制数指定,如果未指定宽度,则表示值时除必需之外不作填充。
精度通过(可选的)宽度后跟点号后跟的十进制数指定。如果未指定精度,会使用默认精度;如果点号后没有跟数字,表示精度为0。 举例如下:
%f:默认宽度,默认精度
%9f:宽度9,默认精度
%.2f:默认宽度,精度2
%9.2f 宽度9,精度2
%9.f 宽度9,精度0

(七)其他flag

'+'总是输出数值的正负号;对%q(%+q)会生成全部是ASCII字符的输出(通过转义)
‘’对数值,正数前加空格而负数前加负号
‘-’在输出右边填充空白而不是默认的左边(即从默认的右对齐切换为左对齐)
‘#’ 切换格式:
八进制数前加0(%#0),十六进制前加(0x)或0X(%#X),指针去掉前面的0x(%#p)
对%q(%#q),如果strconv。CanBackquote返回真会输出反引号括起来的未转义字符串
对%U(%#U),输出Unicode格式后,如字符可打印,还会输出空格和单引号括起来的go字面值
对字符串采用%x或者%X市(%x或%X)会给各打印的字节之间加空格
0’使用0而不是空格填充,对于数值类型会把填充的0放正负号后面

package main

import "fmt"

func main() {
	a := 100

	var b byte = 100
	var c rune = 200
	var d byte = 'a'
	var e rune = 'b'
	var str string = "abcde"

	fmt.Printf("%T , %v\n", a, a)
	fmt.Printf("%T , %v\n", b, b)
	fmt.Printf("%T ,% v\n", c, c)
	fmt.Printf("%T ,% v\n", d, d)
	fmt.Printf("%T ,% v\n", e, e)
	fmt.Printf("%T ,% v\n", str, str)

	temp :=`x := 10
	y := 20
	z := 30
	fmt.Println(x, " ", y, " ", z)
	x, y, z = y, z, x
	fmt.Println(x, " ", y, " ", z)`
	fmt.Println(temp)
}

ASCII字符集中数字的10进制范围是【30-39】
ASCII字符集中大写字母的10进制范围是【65-90】
ASCII字符集中小写字母的10进制范围是【97-122】
Unicode字符集中汉字的范围是【4e00-9fa5】,10进制范围是【19968-40869】

package main

import "fmt"

type Student struct {
	x, y int
}

func main() {
	str := "steven"
	fmt.Printf("%T , %v\n", str, str)

	var a rune = '一'
	fmt.Printf("%T , %v\n", a, a)

	p := Student{1, 2}
	fmt.Printf("%T , %v\n", p, p)

	//布尔值
	fmt.Printf("%t , %t\n", true, true)

	//整型
	fmt.Printf("%T , %d\n", 123, 123)
	fmt.Printf("%T , %5d\n", 123, 123)
	fmt.Printf("%T , %05d\n", 123, 123)
	fmt.Printf("%T , %b\n", 123, 123)

	str = fmt.Sprintf("%b", 123)
	fmt.Println(str)

	fmt.Printf("%x \n", 123)
	fmt.Printf("%X \n", 123)
	fmt.Printf("%U \n", 'a')

	//浮点型
	fmt.Printf("%f \n", 123.1)
	fmt.Printf("%.2f \n", 123.125456)
	fmt.Printf("%.1e \n", 123.125456)

	//arr := []byte{97, 98, 99,65}
	arr2 := []byte{'a', 'b', 'c','A'}
	//字符串
	fmt.Printf("%s \n", "欢迎大家学习区块链")
	fmt.Printf("%q \n", "欢迎大家学习区块链")
	fmt.Printf("%T , %s \n", arr2, arr2)
	fmt.Printf("%T , %x \n", arr2, arr2)
	fmt.Printf("%T , %X \n", arr2, arr2)
}

2.3 数据类型转换

1、T(表达式)
采用数据类型前置加括号的方式进行类型转换。T表示要转换的类型;表达式包括变量、数值、函数返回值等、
类型转换时,需要考虑两种类型之间的关系和范围,是否会发生数值截断
布尔型无法与其他类型进行转换
2、float与int之间转换
需要注意float转int时精度的损失
3、int转string
其实相当于是byte或rune转string
该int数值是ASCII码编号或Unicode字符集的编号。转成string就是将根据字符集,将对应编号的字符查找出来。
当该数值超出Unicode编号范围,则转成的字符串显示为乱码。
例如19968转string,就是“一”
【备注】
ASCII字符集中数字的10进制范围市【30-39】
ASCII字符集中大写字母的10进制范围市【65-90】
ASCII字符集中小写字母的10进制范围市【97-122】
Unicode字符集中汉字的范围是【4e00-9fa5】,10进制范围是【19968-40869】

package main

import (
	"fmt"
)

func main() {
	chinese := 90
	english := 80.9
	avg := (chinese + int(english)) / 2
	avg2 := (float64(chinese) + english) / 2
	fmt.Println(avg)
	fmt.Println(avg2)
	x := 19968
	result := string(rune(chinese))
	result = string(rune(x))
	fmt.Println(result)
}

2.4 常量和itoa

(一)、声明方式
1、相对于变量,常量是恒定不变的值,例如圆周率
常量是一个简单值的标识符,在程序运行时,不会被修改
2、常量中的数据类型只可以是布尔型、数字型(整型、浮点型和复数)和字符串型。
3、常量的定义格式:

const 标识符【类型】=值

可以省略类型说明符【type】,因为编译器可以根据常量的值来自动推断其类型
显式类型定义:

const B string = “1”

隐式类型定义:

const C = “1”

4、多个相同类型的声明可以简写

const WIDTH,HEIGHT=value,value2

5、常量定义后未被使用,不会在编译时出错

(二)、常量用于枚举(常量组)
例如一下格式:

const(
	Unknown=0
	Frmale=1
	Male=2
)

数字0、1和2分别代表未知性别、女性和男性。
常量组中如果不指定类型和初始值,则与上一行非空常量的值相同

	const(
		a=10
		b
		c
	)

打印a、b、c,输出:10 10 10

(三)、iota
1、iota,特殊常量值,是一个系统定义的可以被编译器修改的常量值。iota只能出现在常量中。

2、在每一个const关键字出现时,被重置为0,然后每出现一个常量值,iota所代表的数值会自动增加1。
iota可以理解成常量组中常量的计数器,不论该常量的值是什么,只要有一个常量,那么iota就加1.

3、iota可以被用作枚举值

const(
		a=iota
		b=iota
		c=iota
	)
	print(a,b,c)

打印输出:0 1 2
第一个iota等于0,每当iota在新的一行被使用时,它的值都会自动加1;所以a=0,b=1,c=2

2.5 运算符

(一)算术运算符(Arithmetic operator)
+相加 -相减 *相乘 /相除 %求余 ++自增 --自减

(二)关系运算符(Relational operator)
==等等 !=不等 >大于 <小于 >=大于等于 <=小于等于

(三)逻辑运算符(Logical operator)
&&AND ||OR !NOT

(四)位运算符
位运算符对整数在内存中的二进制位进行操作。
位运算符比一般的算术运算符速度要快,而且可以实现一些算术运算符不能实现的功能。
如果要开发高效率程序,位运算符是必不可少的。
位运算符用来对二进制位进行操作,包括:按位与(&)、按位或(|)、按位异或(^)、按位左移(<<)、按位右移(>>)

假定A=60;B=13;其二进制数转换为:
A=00111100
B=00001101
----------
A&B=00001100 只有两数都为1才是1
A|B=00111101 一个是1就是1
A^B=00110001 两数不同是1

& 按位与运算符是双目运算符。其功能是参与运算的两数各对应的二进位相与。
| 按位或运算符是双目运算符。其功能是参与运算的两数各对应的二进位相或。
^ 按位异或运算符是双目运算符。其功能是参与运算的两数各对应的二进位相异或。

<<左移运算符是双目运算符。其功能是左移n位就是乘以2的n次方
>>左移运算符是双目运算符。其功能是右移n位就是除以2的n次方

package main

import "fmt"

var a = 21.0
var b = 5.0
var c float64

func main() {
	Arithmetic()
	Relational()
	Logical()
}

//算术运算符
func Arithmetic() {
	c = a + b
	fmt.Printf("第 1 行, c值为 = %.2f\n", c)
	c = a - b
	fmt.Printf("第 2 行, c值为 = %.2f\n", c)
	c = a * b
	fmt.Printf("第 3 行, c值为 = %.2f\n", c)
	c = a / b
	fmt.Printf("第 4 行, c值为 = %.2f\n", c)
	//c = a%b
	fmt.Printf("第 5 行, c值为 = %d\n", int(a)%int(b))
	a++
	fmt.Printf("第 5 行, a值为 = %f\n", a)

	a = 21 //为了方便测试,a这里重新赋值21
	a--
	fmt.Printf("第 5 行, a值为 = %f\n", a)
}

//关系运算符
func Relational() {
	if a == b {
		fmt.Printf("第1行 a等于b\n")
	} else {
		fmt.Printf("第1行 a不等于b\n")
	}
	if a < b {
		fmt.Printf("第2行 a小于b\n")
	} else {
		fmt.Printf("第2行 a不小于b\n")
	}
	if a > b {
		fmt.Printf("第3行 a大于b\n")
	} else {
		fmt.Printf("第3行 a不大于b\n")
	}
	/*修改a,b的值*/
	a = 5
	b = 20
	if a <= b {
		fmt.Printf("第4行 a小于等于b\n")
	}
	if b >= a {
		fmt.Printf("第5行 b大于等于a\n")
	}
}

//逻辑运算符
func Logical() {
	a := true
	b := false

	if a && b {
		fmt.Printf("第1行 条件为true")
	}
	if a || b {
		fmt.Printf("第2行 条件为true")
	}
}

Lesson3 语句

3.1 if语句

1、不使用括号将条件包括起来
2、大括号{}必须存在
3、左括号必须在if或else的同一行
4、在if之后,条件语句之前,可以添加变量初始化语句,是哟“;”进行分隔

package main

import "fmt"

func main() {
	EvenOdd()
}

//判断数值奇数偶数
func EvenOdd() {
	num := 30
	if num%2 == 0 {
		fmt.Println(num, "偶数")
	} else {
		fmt.Println(num, "奇数")
	}
}

3.2 switch语句

switch语句用于基于不同条件下执行不同动作,每一个分支都是唯一的,从上至下注意测试,直到匹配为止。
switch语句执行的过程从上至下,直到找到匹配项,匹配项后面也不需要再加break
变量var1可以是任何类型,而val1和val2则可以是同类型的任意值。类型不被局限于常量或者整数,但必须是相同类型;或者最终结果位相同类型表达式。
可以同时测试多个符合条件的值,使用逗号分割它们,例如:case val1,val2,val3
Go语言中switch后的表达式可以省略那么默认是true

package main

import "fmt"

func main() {
	ScoreGrade()
}

func ScoreGrade() {
	score := 98.5
	grade := ""
	switch {
	case score >= 90:
		grade = "A"
	case score >= 80:
		grade = "B"
	case score >= 70:
		grade = "C"
	case score >= 60:
		grade = "D"
	default:
		grade = "E"
	}
	fmt.Printf("你的等级是:%s \n", grade)
	fmt.Printf("你的评价是: ")
	switch grade {
	case "A":
		fmt.Println("优秀")
	case "B":
		fmt.Println("良好")
	case "C":
		fmt.Println("中等")
	case "D":
		fmt.Println("及格")
	default:
		fmt.Println("差")
	}

}
package main

import "fmt"

func main() {
	getDaysByMonth()
}
func getDaysByMonth() {
	//定义局部变量:年、月、日
	year := 2000
	month := 2
	days := 0
	switch month {
	case 1, 3, 5, 7, 8, 10, 12:
		days = 31
	case 4, 6, 9, 11:
		days = 30
	case 2:
		if (year%4 == 0 && year%100 != 0) || year%400 == 0 {
			days = 29
		} else {
			days = 28
		}
	default:
		days = -1
	}
	fmt.Printf("%d年%d月的天数是:%d", year, month, days)
}

1、switch语句执行的过程自上而下,直到找到case匹配项,匹配项中无需使用break。
因为Go语言中的switch默认给每个case自带break,因此匹配成功后不会向下执行整个分支,而是跳出整个break。
2、每个var1可以是任何类型,而val1和val2则可以是同类型的任意值。类型不被局限于常量或整数,但必须是相同类型或最终结果为相同类型的表达式。
3、case后的值不能重复
4、可以同时测试多个符合条件的值,也就是说case后可以有多个值,这些值之间使用逗号分割,例如case val1,val2,val3
5、Go语言中的switch后的表达式可以省略,那么默认是switch true
6、Go语言中的switch case因为自带break,所以匹配某个case后不会自动向下执行其他case。
如需贯通后续的case,可以添加fallthrough(中文含义是:贯穿),强制执行后面的case分支。
7、fallthrough必须放在case分支的最后一行。
如果它出现在中间的某个地方,编译器就会抛出错误(fallthrough statement out of place,含义是fallthrough不在合适的位置)

package main

import "fmt"

func main() {
	eval()
}

func eval() {
	num1, num2, result := 12, 4, 0
	operation := '+'
	switch operation {
	case '+':
		result = num1 + num2
		fallthrough
	case '-':
		result = num1 - num2
		fallthrough
	case '*':
		result = num1 * num2
	case '/':
		result = num1 / num2
	case '%':
		result = num1 % num2
	default:
		result = -1
	}
	fmt.Println(result)
}

3.3 for语句

一)语法形式一
1 语法结构

for 初始语句init;条件表达式condition;结束语句post{
//循环体代码
}

三个组成部分,即初始化、条件表达式和post都是可选的。
因此这种基本的for循环语法结构又能演化出四种略有不同的写法。

2 示例代码

for i:=0;i<=10;i++{
	fmt.Printf("%d",i)
}

3语法解释
(1)初始语句init
初始语句是在第一次循环前执行的语句,一般赋值表达式,给控制变量赋初始值。
如果控制变量在此处被声明,其作用域将被局限在这个for的范围内;在for循环中声明的变量仅在循环范围内可用
初始语句可以省略不写,但是初始语句之后的分号必须要写。

i:=0
for;i<=10;i++{
	fmt.Printf("%d",i)
}

(2)条件表达式condition
条件表达式是控制循环与否的开关
如果表达式为true,则循环继续,否则结束循环
条件表达式可以省略不写,之后的分号必须要写
省略条件表达式默认形成无限循环

i:=0
for;;i++{
	if i>20 {
		break
	}
	fmt.Printf("%d",i)
}

(3)结束语句post
一般为赋值表达式,给控制变量递增或者递减
post语句将在循环的每次成功迭代之后执行

(二)语法形式二
1语法结构

for循环条件condition{}

效果类似其他编程语言中的while循环

2示例代码

var i int
	for i<=10 {
		fmt.Println(i)
		i++
	}

(三)语法形式三(for关键字后无表达式)
1语法结构

for{}

效果与其它编程语言的for(;😉{}一致,此时for执行无限循环

2示例代码

var i int
for{
	if i>10 {
		break
	}
	fmt.Println(i)
}

(四)几个案例

package main

import "fmt"

func main() {
	baseFor()
}

func baseFor() {
	//for循环的基本语法结构第一种
	for i := 0; i <= 10; i++ {
		fmt.Printf("%d ", i)
	}
	fmt.Println("-----------------")
	//第二中形式
	i := 0
	for ; i <= 10; i++ {
		fmt.Printf("%d ", i)
	}
	fmt.Println("-----------------")
	//第三种形式
	for ; ; i++ {
		if i > 20 {
			break
		}
		fmt.Printf("%d ", i)
	}
	fmt.Println("-----------------")
	//第四种形式
	for ; ; {
		if i > 40 {
			break
		}
		i++
		fmt.Printf("%d ", i)
	}
	fmt.Println("-----------------")
}
package main

import "fmt"

func main() {
	summation()
	summation2()
	cutBamboo()
	traverseString()
	traverseSlice()
}

//计算1-100之间的和
func summation() {
	result := 0
	for i := 1; i <= 100; i++ {
		result += i
	}
	fmt.Println(result)
}

//计算1-100之间3的倍数和
func summation2() {
	result := 0
	i := 1
	for i <= 100 {
		if i%3 == 0 {
			result += i
			fmt.Print(i)
			if i < 99 {
				fmt.Print("+")
			} else {
				fmt.Printf("=%d \n", result)
			}
		}
		i++
	}
	//fmt.Println(result)
}

//3截竹竿。32m竹竿,每次截1.5m,最快截几次之后能小于4m
func cutBamboo() {
	count := 0
	for i := 32.0; i >= 4; i -= 1.5 {
		count++
	}
	fmt.Println(count)
}

//4遍历字符串,获得字符
func traverseString() {
	str := "123ABCabc一丁丂"
	for i, value := range str {
		fmt.Printf("第%d位的Unicode是:%v 字符是%c \n", i, value, value)
	}
	//utf-8 utf-6
}

//5遍历切片
func traverseSlice() {
	arr := []int{100, 200, 300}
	for i, value := range arr {
		fmt.Println(i, ":", value)
	}
}

package main

import "fmt"

var lines = 9

func main() {
	printRectangle()
	printRightTriangleLB()
	printRightTriangleLT()
	printRightTriangleRB()
	printRightTriangleRT()
	printEqualTriangle()
	muliple99()
}

//1打印矩形
func printRectangle() {
	fmt.Println("\n打印矩形")
	for i := 1; i <= lines; i++ {
		for j := 1; j <= lines; j++ {
			fmt.Print("♥ ")
		}
		fmt.Println()
	}
}

//2打印左下角直角三角形
func printRightTriangleLB() {
	fmt.Println("\n打印左下直角三角形")
	for i := 0; i < lines; i++ {
		for j := 0; j < i; j++ {
			fmt.Print("♥ ")
		}
		fmt.Println()
	}
}

//3打印左上直角三角形
func printRightTriangleLT() {
	fmt.Println("\n打印左上直角三角形")
	for i := 0; i < lines; i++ {
		for j := lines - 1; j > i; j-- {
			fmt.Print("♥ ")
		}
		fmt.Println()
	}
}

//4打印右下直角三角形
func printRightTriangleRB() {
	fmt.Println("\n打印右下直角三角形")
	for i := 0; i < lines; i++ {
		for m := lines - 1; m > i; m-- {
			fmt.Print("  ")
		}
		for j := 0; j < i; j++ {
			fmt.Print("♥ ")
		}
		fmt.Println()
	}
}

//5打印右上直角三角形
func printRightTriangleRT() {
	fmt.Println("\n打印右上直角三角形")
	for i := 0; i < lines; i++ {
		for m := 0; m < i; m++ {
			fmt.Print("  ")
		}
		for j := lines - 1; j > i; j-- {
			fmt.Print("♥ ")
		}
		fmt.Println()
	}
}

//6打印等腰三角形
func printEqualTriangle() {
	fmt.Println("\n打印等腰直角三角形")
	for i := 0; i < lines; i++ {
		for m := lines - 1; m > i; m-- {
			fmt.Print("  ")
		}
		for j := 0; j < i*2-1; j++ {
			fmt.Print("♥ ")
		}
		fmt.Println()
	}
}

//打印九九乘法表
func muliple99() {
	fmt.Println("\n打印九九乘法表")
	for i := 1; i <= lines; i++ {
		for j := 1; j <= i; j++ {
			fmt.Printf("%d*%d=%d ", j, i, i*j)
		}
		fmt.Println()
	}
}

3.4 break、continue和goto语句

break、continue的区别
break语句将无条件跳出并结束当前的循环,然后执行循环体后的语句;
continue语句是跳过当前的循环,而开始执行下一次循环。
goto语句通常与条件语句配合使用。可用来实现条件转移,构成混乱,跳出循环体等功能。
但是,在结构化程序设计中一般不主张使用goto语句,以免造成程序流程的混乱,使理解和调试程序都产生困难
goto语法格式如下:
LABEL:statement
goto LABEL

package main

import "fmt"

func main() {
	//break和continue的区别
	breakContinue()
	//输出1-50之间所有不包含4的数字(个位和十位)continue实现
	eludeFour()
	//输出1-50之间所有不包含4的数字(个位和十位)goto实现
	eludeFourGoto()
	//输出1-100素数
	printPrimeNumber()
}
func breakContinue() {
	fmt.Println("\n1、break、continue的区别")
	//1、break终止循环
	for i := 0; i < 10; i++ {
		if i == 5 {
			break
		}
		fmt.Print(i)
	}
	fmt.Println("\n2、continue的跳过某次循环")
	//2、continue跳过某次循环
	for i := 0; i < 10; i++ {
		if i == 5 {
			continue
		}
		fmt.Print(i)
	}
}

//输出1-50之间所有不包含4的数字(个位和十位)
func eludeFour() {
	fmt.Println("\n输出1-50之间所有不包含4的数字")
	num := 0
	for num < 50 {
		num++
		if num%10 == 4 || num/10%10 == 4 {
			continue
		}
		fmt.Printf("%d\t", num)
	}
}

//输出1-50之间所有不包含4的数字(个位和十位)goto实现
func eludeFourGoto() {
	fmt.Println("\n输出1-50之间所有不包含4的数字。goto实现")
	num := 0
LOOP:
	for num < 50 {
		num++
		if num%10 == 4 || num/10%10 == 4 {
			goto LOOP
		}
		fmt.Printf("%d\t", num)
	}
}

//输出1-100的素数(借助goto跳转)
func printPrimeNumber() {
	fmt.Println("\n1-100的素数(借助goto跳转)")
	num := 0
LOOP:
	for num < 100 {
		num++
		for i := 2; i < num; i++ {
			if num%i == 0 {
				goto LOOP
			}
		}
		fmt.Printf("%d\t", num)
	}
}

Lesson 4 函数

4.1什么是函数

1、函数是组织好的、可重复使用的执行特定任务的代码块。它可以提高应用程序的模块性和代码的重复利用率。
2、Go语言支持普通函数、匿名函数和闭包,从设计上对函数进行了优化和改进,让函数使用起来更加方便。
3、Go语言的函数属于一等公民(first-class):
函数本身可以作为值进行传递
支持匿名函数和闭包(closure)
函数可以满足接口

4.2 声明函数

1、函数声明的作用
普通函数需要先声明才能调用,一个函数的声明包括参数和函数名等。
编译器通过声明才能了解函数应该怎样在调用代码和函数体之间传递参数和返回函数
2、语法格式

func 函数名 (参数列表) (返回参数列表) {
	//函数体
}
func funcName(parametername type1,parametername type2...)(output1 type1,output2 type2...){
		//逻辑代码
		//返回多个值
		return value1,value2...
	}

3、函数定义解析
func:函数关键字
函数由func开始声明

funcName:函数名
函数名和菜蔬列表一起构成了函数签名
函数名由字母、数字和下划线组成,函数名的第一个字母不能为数字
在同一个包内,函数名称不能重名

parametername type:参数列表
参数就像一个占位符,定义函数时的参数叫做形式参数,形参变量是局部变量;
当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。
参数列表指定的是参数类型、顺序、及参数的个数
参数是可选的,也就是说函数也可以不包含参数
参数类型的简写
在参数列表中,如果有多个参数变量,则以逗号分隔离
例如:

func add(a,b int) {}

Go语言的函数支持可变参数。接受变参的函数是有着不定数量的参数的。

func myfunc(arg …int) {}

arg …int告诉Go这个函数接受不定数量的参数。注意,这些参数的类型全部是int。在函数体中,变量arg是一个int的slice,output1 type1,output2 type2:返回值列表

Go语言的函数可以返回多个值
返回值可以是:返回数据的类型,或者是:变量名+变量类型的组合
函数声明时有返回值,必须在函数体中使用return语句提供返回值列表
如符合只有一个返回值且不声明返回值变量,那么可以省略包括返回值的括号

函数体:函数定义的代码集合,是能够被重复调用的代码片段

4.3 作用域

1、概述
作用域是变量、常量、类型、函数的作用范围
Go语言中变量可以在三个地方声明:
函数内定义的变量称为局部变量
函数外定义的变量称为全局变量
函数中定义的参数称为形式参数
2、局部变量
在函数体内声明的变量称之为局部变量。它们的作用域只在函数体内,参数和返回值的变量也是局部变量
3、全局变量
在函数体外声明的变量称之为全局变量,全局变量可以在整个包甚至外部包(被导出后)使用
全局变量可以在任何函数中使用。
Go语言程序中全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑。
4、形式参数
形式参数会作为函数的局部变量来使用。

package main

import "fmt"

//声明全局变量
var a int = 7
var b int = 9

func main() {
	//声明局部变量
	a, b, c := 10, 20, 0
	fmt.Printf("main()函数中a=%d\n", a) //10
	fmt.Printf("main()函数中b=%d\n", b) //20
	fmt.Printf("main()函数中c=%d\n", c) //0

	c, _ = sum2(a, b)
	fmt.Printf("main()函数中c=%d\n", c) //?

}

//两个数值相加
func sum(a, b int) (int, int) {
	a++        //11
	b += 2     //22
	c := a + b //33
	d := a * b
	fmt.Printf("sum()函数中a=%d\n", a) //11
	fmt.Printf("sum()函数中b=%d\n", b) //22
	fmt.Printf("sum()函数中c=%d\n", c) //33
	return c, d
}

func sum2(a, b int) (c, d int) {
	a++       //11
	b += 2    //22
	c = a + b //33
	d = a * b
	fmt.Printf("sum2()函数中a=%d\n", a) //11
	fmt.Printf("sum2()函数中b=%d\n", b) //22
	fmt.Printf("sum2()函数中c=%d\n", c) //33
	return
}

4.4 函数变量(函数作为值)

在Go语言中,函数也是一种类型,可以和其它类型一样被保存在变量中
可以通过type来定义一个自定义类型。函数的参数完全相同(包括:参数类型、个数、顺序),函数返回值相同

package main

import (
	"fmt"
	"strings"
)

func main() {
	str:="abcdDEFfgkhIJKLmnopQ"
	//result:=processLetter(str)
	//fmt.Println(result)
	//使用函数变量的普通用法
	fmt.Println(StringToCase(str,processLetter))
	//使用type的用法
	fmt.Println(StringToCase2(str,processLetter))
}

//处理字符串,实现奇偶交替
func processLetter(str string)string{
	result:=""
	for i,value:=range str{
		if i%2==0{
			result+=strings.ToUpper(string(value))
		}else{
			result+=strings.ToLower(string(value))
		}
	}
	return result
}

func StringToCase(str string,myfunc func(string)string) string{
	return myfunc(str)
}

type caseFunc func(string) string
func StringToCase2(str string,myfunc caseFunc) string{
	return myfunc(str)
}

函数变量的使用定义:
1、定义一个函数类型
2、实现定义的函数类型
3、作为参数调用

函数变量的用法类似接口的用法
函数当做值和类型在写一些通用接口的时候非常有用。
通过上面例子可以看到processFunc这个类型是一个函数类型,
然后两个filter函数的参数和返回值与processFunc类型是一样的
用户可以实现很多种逻辑,这样使得程序变得非常灵活

package main

import "fmt"

type myFunc func(int) bool

func main() {
	arr :=[]int{23,4,5,6,8,9,10,345,789,124}
	fmt.Println("slice=",arr)
	//获取切片中的奇数元素
	odd := Filter(arr,isOdd)
	fmt.Println("奇数元素:",odd)
	//获取切片中的偶数元素
	even := Filter(arr,isEven)
	fmt.Println("偶数元素:",even)
}

//判断整型元素是偶数
func isEven(num int) bool {
	if num%2 == 0 {
		return true
	}
	return false
}

//判断元素是奇数
func isOdd(num int) bool {
	if num%2 == 0 {
		return false
	}
	return true
}

//根据函数来处理切片,实现奇数偶数分组,返回新的切片
func Filter(arr []int,f myFunc)[]int{
	var result []int
	for _,value:=range arr{
		if f(value){
			result=append(result,value)
		}
	}
	return result
}

4.5 匿名函数

1、概念
Go语言支持匿名函数,即在需要使用函数时,再定义函数,匿名函数没有函数名,只有函数体,函数可以被作为一种类型被赋值给变量。
匿名函数也往往以变量方式被传递
匿名函数经常被用于实现回调函数、闭包等。
2、定义格式

func(参数列表)(返回参数列表){
	//函数体
}
package main

import (
	"fmt"
	"math"
)

func main() {
	//1、定义时调用无参匿名函数
	func(data int) {
		fmt.Println("你的成绩", data)
	}(98)

	//2、定义时调用有参匿名函数
	result := func(data float64) float64 {
		return math.Sqrt(data)
	}(250)
	fmt.Println("平方根", result)

	//3、将匿名函数赋值给变量,需要时再调用
	myfunc := func(data string) string {
		return data
	}
	fmt.Println(myfunc("欢迎学习Go语言"))
}

匿名函数作为回调函数

package main

import (
	"fmt"
	"math"
)

type myFuncs func(float642 float64) string

func main() {
	//1、定义切片,对其中的数据进行求平方根和求平方的运算
	arr := []float64{1, 9, 16, 25, 30}
	result := FilterSlice(arr, func(val float64) string {
		val = math.Pow(val,2)
		return fmt.Sprintf("%.0f", val)
	})
	fmt.Print(result)
}

//遍历切片,对其中每个元素进行运算处理
func FilterSlice(arr []float64, f myFuncs) []string {
	var result []string
	for _, value := range arr {
		result = append(result, f(value))
	}
	return result
}

4.6 闭包

1、概念
闭包并不是什么新奇的概念,它早在高级语言开始发展的年代就产生了。
闭包(Closure)是词法闭包(Lexical Closure)的简称。
对闭包的具体定义有很多种说法,大体可以分为两类:
一种说法认为闭包是符合一定条件的函数,比如这样定义闭包:闭包是在其词法上下文中引用了自由变量的函数、
另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。比如这样的定义:
在实现深约束是,需要创建一个能显示表示引用环境的东西,
并将它与相关的子程序捆绑在一起,这样捆绑起来的整体称为闭包。函数+引用环境=闭包
上面的定义,一个认为闭包是函数,另一个认为闭包是函数和引用环境组成的整体。显然第二种说法更确切。
闭包只是在形式和表现上像函数,但实际上不是函数。
函数是一些可执行的代码,这些代码在函数被定义后就确定了,不会在执行时发生变化,所以一个函数只有一个实例
闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
闭包在某些语言中被称为Lambda表达式。
函数本身不存储任何信息,只有与引用环境结合后形成的闭包才具有”记忆性“。函数是编译器静态的概念,而闭包是运行期动态的概念。
对象是附有行为的数据,而闭包是附有数据的行为。

2、闭包的价值
(1)、加强模块化
闭包有益于模块化编程,它能以简单的方式开发较小的模块,从而提高开发速度和程序的可复用性。
和没有闭包的程序相比,使用闭包可将模块划分得更小。
比如我们要计算一个数组中所有数字的和,这只需要循环遍历数组,把遍历的数字加起来就行了。
如果现在要计算所有元素的积呢?要打印所有的元素呢?解决这些问题都要对数组进行遍历。
如果是在不支持闭包的语言中,我们不得不一次又一次重复地写循环语句。而这在支持闭包的语言中是不必要的。
这种处理方法多少有点像回调函数,不过要比回调函数写法更简单,功能更强大。
(2)、抽象
闭包是数据和行为的组合,这使得闭包具有较好抽象能力
(3)、简化代码

3、一个编程语言需要哪些特性来支持闭包呢?
函数是一阶值(First-class value,一等公民),即函数可以作为另一个函数的返回值或参数,还可以作为一个变量的值。
函数可以嵌套定义,即在一个函数内部可以定义另一个函数
允许定义匿名函数
可以捕获引用环境,并把引用环境和函数代码组成一个可调用的实体;

不使用闭包实现计数器

//不使用闭包实现计数器
package main

import "fmt"

func main() {
	for i := 0; i < 10; i++ {
		fmt.Printf("i=%d \t", i)
		fmt.Println(add(i))
	}
}

//用来计数的函数
func add(num int) int {
	sum := 0
	sum += num
	return sum
}

使用闭包实现计数器

package main

import "fmt"

func main() {
	res := adder()

	fmt.Printf("%T \n", res)

	for i := 0; i < 5; i++ {
		fmt.Printf("i=%d \t", i)
		fmt.Println(res(i))
	}
}

//实现计数器的闭包函数
func adder() func(int) int {
	sum := 0
	res := func(num int) int {
		sum += num
		return sum
	}
	return res
}
package main

import "fmt"

func main() {
	res := Counter()
	fmt.Printf("%T\n", res)
	fmt.Println("res", res)
	fmt.Println("res()", res()) //1
	fmt.Println("res()", res()) //2
	fmt.Println("res()", res()) //3

	res2 := Counter()
	fmt.Println("res2", res2)
	fmt.Println("res2()", res2()) //1
	fmt.Println("res2()", res2()) //2
	fmt.Println("res2()", res2()) //3
}

//闭包函数,实现计数器功能
func Counter() func() int {
	i := 0
	res := func() int {
		i++
		return i
	}
	fmt.Println("Counter内部", res)
	return res
}

闭包的另一种写法

package main

import "fmt"

func main() {
	res := func() func() int {
		i := 10
		return func() int {
			i++
			return i
		}
	}()
	fmt.Println(res())
}

4.7 可变参数

1、如果一个函数的参数,类型一致,但个数不定,可以使用函数的可变参数
2、语法格式:

func函数名(参数名 ...类型)[(返回值列表)]{
			//函数体
	}

该语法格式定义了一个接受任何数目、任何类型参数的函数。这里特殊的语法是三个点“…”
在一个变量后面加上三个点后,表示从该处开始接受不定参数。
当要传递若干个值到不定参数函数中的时候,可以手动书写每个参数,也可以将一个slice传递给该参数
通过“…”可以将slice中的参数对应的传递给函数

package main

import "fmt"

func main() {
	//1、传n个成绩的用法
	sum, avg, count := GetScore(90, 78, 87, 67, 65, 55)
	fmt.Printf("学员共有%d门成绩,总成绩为:%.2f,平均成绩为%.2f", count, sum, avg)

	//2、传切片的用法
	scores := []float64{90, 78, 87.5, 67, 65, 55, 100, 94}
	sum, avg, count = GetScore(scores...)
	fmt.Printf("\n学员共有%d门成绩,总成绩为:%.2f,平均成绩为%.2f", count, sum, avg)
}

//累加求总分及平均分
func GetScore(scores ...float64) (sum, avg float64, count int) {
	for _, value := range scores {
		sum += value
		count++
	}
	avg = sum / float64(count)
	return
}

/*
	可变参数的注意细节
		一个函数最多只能有一个可变参数
		参数列表中还有其它类型参数,则可变参数写在所有参数的最后
*/

4.8递归函数

1、在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。
递归函数必须满足以下两个条件:
1)在每一次调用自己时,必须是(某种意义上)更接近于解;
2)必须有一个终止处理或计算的准则

2、案例代码:求阶乘
计算阶乘n!=123*…n,用函数fact(n)表示.
可以看出:fact(n)=n!=1
23…*(n-1)*n=fact(n-1)n
所以,fact(n)可以表示为n
fact(n-1),只有n=1时需要特殊处理

3、使用递归的注意事项

		递归的计算过程
		===>factorial(5)
		===>5*factorial(4)
		===>5*(4*factorial(3))
		===>5*(4*(3*factorial(2)))
		===>5*(4*(3*(2*factorial(1))))
		===>5*(4*(3*(2*1)))
		===>5*(4*(3*2))
		===>5*(4*6)
		===>5*24
		===>120

递归函数的优点是定义简单,逻辑清晰。
理论上,所有的递归函数都可以用循环的方式实现,但循环的逻辑不如递归清晰
使用递归函数需要注意防止栈溢出。
在计算机中,函数调用是通过栈(stack)这种数据结构实现的。
每当进入一个函数调用,栈就会加一层栈,每当函数返回,栈就会减一层。
由于栈的大小不是无限的,所以,递归调用的次数过多,就会导致栈溢出。
使用递归函数的有点时逻辑简单清晰,缺点是过深调用会导致栈溢出。

Lesson 5 指针

5.1 指针的概述

1、指针是存储另一个变量的内存地址变量
变量是一种使用方便的占位符,变量都指向计算机的内存地址
一个指针变量可以指向任何一个值的内存地址
如下图:变量b的值为156,存储在内存地址0x1040a124。变量a持有b的地址,则a被认为指向b、
2、获取变量的地址
Go语言的取地址符&,一个变量前使用&,会返回该变量的内存地址
3、Go语言指针的特点
Go语言指针最大的特点是:指针不能运算(不同于C语言)
在Go语言中如果对指针进行运算会报错:nvalid operation:p++(non-numeric type *int)

5.2 声明指针

1、声明指针,T是指针变量的类型,它指向T类型的值
var指针变量名
指针类型
*号用于指定变量是一个指针

var ip *int //指向整型的指针
var fp *float32//指向浮点型的指针

2、如何使用指针?(指针使用流程)
定义指针变量
为指针变量赋值
访问指针变量中指向地址的值
获取指针的值:在指针类型的变量前加上*号(前缀)来获取指针所指向的内容。
获取一个指针意味着访问指针指向的变量的值。

语法是:*a

package main

import "fmt"

func main() {
	a := 10
	fmt.Printf("变量的地址:%x \n", &a)

	b := []int{1, 3, 5, 7}
	fmt.Printf("变量的地址:%x \n", &b)

}
package main

import (
	"fmt"
)

func main() {
	//实际变量
	a := 120

	//声明指针变量
	var ip *int

	//给指针变量赋值,将变量a的地址赋值给ip
	ip = &a

	//打印a的类型和值
	fmt.Printf("a 的类型是%T,值是%v \n", a, a)
	//打印&a的类型和值
	fmt.Printf("&a 的类型是%T,值是%v \n", &a, &a)
	//打印ip的类型和值
	fmt.Printf("ip 的类型是%T,值是%v \n", ip, ip)
	//打印*ip的类型和值
	fmt.Printf("ip 的类型是%T,值是%v \n", *ip, *ip)
	//打印*&a的类型和值
	fmt.Printf("ip 的类型是%T,值是%v \n", *&a, *&a)

	fmt.Println(a, &a, *&a)
	fmt.Println(ip, &ip, *(&ip), &(*ip))
}

声明复合类型指针

package main

import "fmt"

type Student struct {
	name    string
	age     int
	married bool
	sex     int8
}

func main() {
	s1 := Student{"Steven", 35, true, 1}
	s2 := Student{"Sunny", 20, false, 0}

	var a *Student = &s1
	var b *Student = &s2
	fmt.Println("\n-----------------------")
	fmt.Printf("s1类型为%T,值为%v \n", s1, s1)
	fmt.Printf("s2类型为%T,值为%v \n", s2, s2)
	fmt.Println("\n-----------------------")
	fmt.Printf("a类型为%T,值为%v \n", a, a)
	fmt.Printf("b类型为%T,值为%v \n", b, b)
	fmt.Println("\n-----------------------")
	fmt.Printf("*a类型为%T,值为%v \n", *a, *a)
	fmt.Printf("*b类型为%T,值为%v \n", *b, *b)

	fmt.Println("\n-----------------------")
	fmt.Println(s1.name, s1.age, s1.married, s1.sex)
	fmt.Println(a.name, a.age, a.married, a.sex)
	fmt.Println((*a).name, (*a).age, (*a).married, (*a).sex)
	fmt.Println(&a.name, &a.age, &a.married, &a.sex)
}

5.4空指针

Go空指针
当一个指针被定义后没有分配到任何变量是,它的值为nil
nil指针也成为空指针
nil在概念上和其它语言的null、None、NULL一样,都指代零值或者空值
一个指针变量通常缩写为ptr

package main

import "fmt"

func main() {
	var ptr *int
	fmt.Printf("ptr类型为%T,值为%v \n", ptr, ptr)
	if ptr==nil{
		fmt.Println("空指针")
	}else{
		fmt.Println("非空指针")
	}
}

5.5 操作指针改变变量数值

package main

import "fmt"

func main() {
	a := 10
	//b:=&a
	var b *int = &a
	fmt.Printf("b的类型%T,数值%v\n", b, b)
	fmt.Println("a的地址", b)
	fmt.Println("*b的值", *b) //10

	*b++
	fmt.Println("a的值", a)

}

5.6 指针作为函数参数

1、change

package main

import "fmt"

func main() {
	a := 10
	fmt.Println("函数调用前的a值", a)
	b := &a
	change(b)
	fmt.Println("函数调用之后a值", a)
}

func change(num *int) {
	*num = 20
}

2、swap

package main

import "fmt"

func main() {
	//定义两个局部变量
	a, b := 100, 200

	//返回值的写法实现数据交换
	a, b = swap0(a, b)
	fmt.Println("第一次交换后", a, b)

	//使用指针实现交换
	swap(&a, &b)
	fmt.Println("第二次交换后", a, b)
}

//具有返回值的惯用写法,实现两个数据交换
func swap0(x, y int) (int, int) {
	return y, x
}

func swap(x, y *int) {
	*x, *y = *y, *x
}

3、切片作为函数变量参数

package main

import "fmt"

func main() {
	a := []int{1, 2, 3, 4}
	fmt.Println("调用函数前切片:",a)
	modify(&a)
	fmt.Println("调用函数后切片:",a)
}
func modify(arr *[]int) {
	(*arr)[0] = 250
}

/*
	虽然将指针传递给一个切片作为函数的参数,可以实现对该切片中元素的修改,但这并不是实现这一目标的惯用方法。
	惯用做法是使用切片。
*/

5.7 指针数组

指针数组:就是元素为指针类型的数组。
定义一个指针数组,例如:var ptr [3]string
有一个元素个数相同的数组,将该数组中每个元素的地址赋值给该指针数组。也就是说该指针与某一个数组完全队医
可以通过
指针变量获取到该地址所对应的值

package main

import "fmt"

const COUNT int = 4

func main() {
	a := [COUNT]string{"abc", "ABC", "123", "一二三"}
	//查看数组的指针的类型和值
	fmt.Printf("%T,%v\n", &a, &a)
	//定义指针数组
	var ptr [COUNT]*string
	fmt.Printf("%T,%v \n", ptr, ptr)

	for i := 0; i < COUNT; i++ {
		//将数组中每个元素的地址赋值给指针数组的每个元素
		ptr[i] = &a[i]
	}
	fmt.Printf("%T,%v \n", ptr, ptr)

	fmt.Println(ptr[0])
	//根据指针数组元素的每个地址获取该地址所指向的元素的真实数值
	for i := 0; i < COUNT; i++ {
		fmt.Println(*ptr[i])
	}
	for _, value := range ptr {
		fmt.Println(*value)
	}
}

5.8指针的指针

1、如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量
当定义一个指向指针的指针变量时,第一和执政存放第二个指针的地址,第二个指针存放变量的地址

address->Address->value

2、指向指针的指针声明格式如下

var ptr **int

以上指向指针的指针变量为整型
访问指向指针的指针变量值需要同时使用两个*号

package main

import "fmt"

func main() {
	var a int
	var ptr *int
	var pptr **int

	a = 123
	//为指针赋值
	ptr = &a
	fmt.Println("ptr:", ptr)
	//为pptr赋值
	pptr = &ptr
	fmt.Println("pptr", pptr)

	//获取指针对应的值
	fmt.Printf("变量a=%d \n", a)
	fmt.Printf("指针变量*ptr=%d \n", *ptr)
	fmt.Printf("指向指针的变量**ptr:%d\n", **pptr)

}

Lesson 6 函数传值和传引用

函数如果使用参数,该参数变量称为函数的形参。形参就像定义在函数体内的局部变量。
调用函数,可以通过两种方式来传递参数。即:值传递和引用传递,或者叫传值和传引用

6.1 值传递(传值)

值传递:是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到原内容数据。
默认情况下,Go语言使用的是值传递,即在调用过程中不会影响到原内容数据
每次调用函数,都将实参拷贝一份在传递到函数中。每次拷贝一份,性能是不是就下降了呢?
其实Go语言中使用指针和值传递配合就避免了性能降低问题,也就是通过传指针参数来解决实参拷贝的问题。

6.2 引用传递(传引用)

1、概念:
引用传递:是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到原内容数据。
严格来说Go语言只有值传递一种传参方式,Go语言是没有引用传递的。
Go语言中可以借助传指针来实现引用传递的效果。函数参数使用指针函数,传参是其实是在拷贝一份指针参数,也就是拷贝了一份变量地址。
函数的参数如果是指针,当调用函数是,虽然参数仍然是按拷贝传递的,但是此时仅仅是拷贝一个指针。
也就是一个内存地址,这样就不用担心实参拷贝造成的内存浪费、时间开销、性能降低的情况、
2、引用传递的作用
传指针使得多个函数能操作同一个对象。
传指针更加轻量级(8bytes),只需要传内存地址。
如果参数是非指针参数,呢么值传递的过程中,每次在拷贝上面就会花费相对较多的系统开销(内存和时间)
所以当要传递大的结构体的时候,用指针是一个明智的选择。
Go语言中slice、map、chan类型的实现机制都是类型指针,所以可以直接传递,而不必取地址后传递指针

传int类型

package main

import "fmt"

func main() {
	a := 10
	fmt.Printf("1、变量a的内存地址:%p,值为:%v \n\n", &a, a)
	//传值
	changeIntVal(a)
	fmt.Printf("2、changeIntVal函数调用后:变量a的内存地址:%p,值为:%v \n\n", &a, a)
	//传指针参数,模仿传引用效果
	changeIntPtr(&a)
	fmt.Printf("3、changeIntVal函数调用后:变量a的内存地址:%p,值为:%v \n\n", &a, a)
}
func changeIntVal(a int) {
	fmt.Printf("-----changeIntVal函数内:值参数a的内存地址:%p,值为:%v \n", &a, a)
	a = 90
}
func changeIntPtr(a *int) {
	fmt.Printf("-----changeIntPtr函数内:值参数a的内存地址:%p,值为:%v \n", &a, a)
	*a = 50
}

传string类型

package main

import (
	"fmt"
	"strings"
)

func main() {
	a := "abcd"
	fmt.Printf("1、变量a的内存地址:%p,值为:%v \n\n", &a, a)
	//传值
	changeStringVal(a)
	fmt.Printf("2、changeStringVal函数调用后:变量a的内存地址:%p,值为:%v \n\n", &a, a)
	//传指针参数,模仿传引用效果
	changeStringPtr(&a)
	fmt.Printf("3、changeStringVal函数调用后:变量a的内存地址:%p,值为:%v \n\n", &a, a)
}
func changeStringVal(a string) {
	fmt.Printf("-----changeStringVal函数内:值参数a的内存地址:%p,值为:%v \n", &a, a)
	a = strings.ToUpper(a)
}
func changeStringPtr(a *string) {
	fmt.Printf("-----changeStringPtr函数内:值参数a的内存地址:%p,值为:%v \n", &a, a)
	*a = strings.ToUpper(*a)
}

传bool类型

package main

import (
	"fmt"
)

func main() {
	a := true
	fmt.Printf("1、变量a的内存地址:%p,值为:%v \n\n", &a, a)
	//传值
	changeBoolVal(a)
	fmt.Printf("2、changeBoolVal函数调用后:变量a的内存地址:%p,值为:%v \n\n", &a, a)
	//传指针参数,模仿传引用效果
	changeBoolPtr(&a)
	fmt.Printf("3、changeBoolVal函数调用后:变量a的内存地址:%p,值为:%v \n\n", &a, a)
}
func changeBoolVal(a bool) {
	fmt.Printf("-----changeBoolVal函数内:值参数a的内存地址:%p,值为:%v \n", &a, a)
	a = false
}
func changeBoolPtr(a *bool) {
	fmt.Printf("-----changeBoolPtr函数内:值参数a的内存地址:%p,值为:%v \n", &a, a)
	*a = false
}

传数组类型

package main

import (
	"fmt"
)

func main() {
	a := [4]int{1, 2, 3, 4}
	fmt.Printf("1、变量a的内存地址:%p,值为:%v \n\n", &a, a)
	//传值
	changeArrayVal(a)
	fmt.Printf("2、changeArrayVal函数调用后:变量a的内存地址:%p,值为:%v \n\n", &a, a)
	//传指针参数,模仿传引用效果
	changeArrayPtr(&a)
	fmt.Printf("3、changeArrayVal函数调用后:变量a的内存地址:%p,值为:%v \n\n", &a, a)
}
func changeArrayVal(a [4]int) {
	fmt.Printf("-----changeArrayVal函数内:值参数a的内存地址:%p,值为:%v \n", &a, a)
	a[0] = 99
}
func changeArrayPtr(a *[4]int) {
	fmt.Printf("-----changeArrayPtr函数内:值参数a的内存地址:%p,值为:%v \n", &a, a)
	(*a)[1] = 250
}

6.3 值传递和引用传递的注意细节【重要】

1、Go语言中所有的传参都是值传递(传值),都是一个副本,一个拷贝。
拷贝的内容有时候是非引用类型(int、string、bool、Array、struct属于非引用类型),这样就在函数中无法修改原有内容数据。
有的是引用类型(指针、slice、map、chan属于引用类型),这样就可以修改原内容数据
2、是否可以修改原内容数据、和传值、传引用没有必然的关系。在C++中,传引用肯定是可以修改原内容数据的,
在Go语言里,虽然只有传值,但是我们也可以修改原内容数据,因为参数可以是引用类型。
3、传引用和传引用类型是两个概念。虽然Go语言只有传值一种方式,但是可以通过传引用类型变量达到跟传引用一样的效果。

传切片类型

package main

import (
	"fmt"
)

func main() {
	a := []int{1, 2, 3, 4}
	fmt.Printf("1、变量a的内存地址:%p,值为:%v \n\n", &a, a)
	//传值
	changeSliceVal(a)
	fmt.Printf("2、changeSliceVal函数调用后:变量a的内存地址:%p,值为:%v \n\n", &a, a)
	//传指针参数,模仿传引用效果
	changeSlicePtr(&a)
	fmt.Printf("3、changeSliceVal函数调用后:变量a的内存地址:%p,值为:%v \n\n", &a, a)
}
func changeSliceVal(a []int) {
	fmt.Printf("-----changeSliceVal函数内:值参数a的内存地址:%p,值为:%v \n", &a, a)
	a[0] = 99
}
func changeSlicePtr(a *[]int) {
	fmt.Printf("-----changeSlicePtr函数内:值参数a的内存地址:%p,值为:%v \n", &a, a)
	(*a)[1] = 250
}

传结构体类型

package main

import (
	"fmt"
)

type Teacher struct {
	name    string
	age     int
	married bool
	sex     int8
}

func main() {
	a := Teacher{"Steven", 35, true, 1}
	fmt.Printf("1、变量a的内存地址:%p,值为:%v \n\n", &a, a)
	//传值
	changeStructVal(a)
	fmt.Printf("2、changeStructVal函数调用后:变量a的内存地址:%p,值为:%v \n\n", &a, a)
	//传指针参数,模仿传引用效果
	changeStructPtr(&a)
	fmt.Printf("3、changeStructVal函数调用后:变量a的内存地址:%p,值为:%v \n\n", &a, a)
}
func changeStructVal(a Teacher) {
	fmt.Printf("-----changeStructVal函数内:值参数a的内存地址:%p,值为:%v \n", &a, a)
	a.name = "Josh"
	a.age = 29
	a.married = false
}
func changeStructPtr(a *Teacher) {
	fmt.Printf("-----changeStructPtr函数内:值参数a的内存地址:%p,值为:%v \n", &a, a)
	a.name = "Daniel"
	a.age = 20
	a.married = false
}

写在最后

我用了大概半个月晚上的时间,每天看视频,然后跟着老师打代码,学习了基本的Go语言知识,由于还是稍微会一点(微乎其微)其他语言,所以呢理解起来并不难。之后我学习了Gin框架,和Vue前端,写个登录注册连个数据库没问题了。开发个类似CMS的内网内容发布管理系统我觉得还是有点困难的,毕竟一个人前后端啥的包括作图都要自己搞。我就慢慢来了,啥都用简化版,B站的视频好像是播放量比较高的那个。这个也是基础的基础的基础,计算机世界太无穷了,学了半天,自己还是小白。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值