【golang简明入门进阶指南01】golang基础变量、函数、条件控制

说明

本文目的:学习golang 必须掌握的基本语法和概念
前置条件:搭建号golang环境并输出helloworld

一、变量和常量

这部分要分清golang语言的优势特性

1.1 变量声明

  1. 名称在前,类型在后,重点突出
  2. 变量声明既有初值,不存在null
  3. 一旦声明必须使用,编译器决定
  4. 可以不声明类型,编译器做类型推断
  5. var和: 等同,后者更短,短就是好

注 := 只能在函数内使用,不能声明全局变量

详细声明方法请参考代码(建议逐行演练一遍)

package main

import "fmt"

//声明全局变量 方法1,2,3都可以用,但是方法4不可以
var g1 int
var g2 int =100
var g3 ="100"
//g4:="200"  编译报错

func main()  {
	// 一、声明单个变量
	// 方法1 声明变量a,默认值是0
	var a int
	fmt.Println("val of a =",a)
	// 方法2 声明变量b,并赋予初始值
	var b int =100
	fmt.Println("val of b = ",b)
	//方法3 声明变量并赋值,通过类型推断判断类型
	var c = "100"
	fmt.Println("val of c =",c)
	fmt.Printf("type of c is %T\n",c)
	//方法4 (最常用 冒号赋值) 省略var直接类型匹配,即初始化并赋值
	var d= 100
	fmt.Println("val of d=",d)
	fmt.Printf("type of d is %T\n",d)


	//二、全局变量测试
	// tips: 和上面这一致,方法4不可用
	fmt.Println("g1=",g1,"g2=",g2,"g3=",g3)

	//三、声明多个变量
	// 同类型变量直接声明类型
	var v1,v2,v3 int = 1,3,4
	fmt.Println("v1,v2,v3 = ",v1,v2,v3)
	// 不同类型变量可以类型推断
	var v4,v5,v6 = 0,"2",3.4
	fmt.Println("v4,v5,v6 = ",v4,v5,v6)
	fmt.Printf("type of v4,v5,v6 are %T %T %T \n",v4,v5,v6)
	// 多行写法,注意小括号且不需要逗号分割
	var (
		v7 int =100
		v8 bool=true
	)
	fmt.Println("v7,v8=",v7,v8)

}


1.2 变量类型

类型含义默认值
boolrow 1col 2
string默认空字符串“”
int整数0
int8,16,32,64
uintptr指针
byte字节
rune32位,为了适应各种编码。没有char
float32,float64浮点型
complex64,complex128复数,暂不考虑

go语言必须显式的强制类型转换,类型必须保持匹配

1.3 常量

常量是只读的,不能修改,声明过程和变量基本一致

  1. 常量和变量都可以定义在函数外,作为全局使用
  2. 常量声明不能使用:=简写
  3. 常量可以“仅声明不使用”,不会报错。
  4. go里面大小写有特殊含义,常量不需要大写。
// tip1: 可以声明但不使用
// tip2: golang中大写代表包可见性,常量不需要大写
const url = "http://www.baidu.com"
func main(){
	// tip3: 常量声明和变量基本一致
	const c1=100
	const (
		c2 = 3.4
		c3 = true
	)
	
	fmt.Println("c1,c2,c3 =", c1,c2,c3)
	
	//tip4: 常量声明不能使用冒号简写
	//const c5:="sdf"
	
	//tip5: 常量是只读的,不能修改
	//c1=200
}

1.4 枚举类型和iota

golang中枚举类型就是声明一组常量,比java中常量更精简。

	//枚举类型
	const (
		java   = 1
		python = 2
		golang = 3
	)
	fmt.Println(java, python, golang)

第一个值=iota表示枚举值自增

# iota可以是表达式表述自增算法
	//使用iota关键字
	const (
		a = iota*2 + 3
		b
		c
		d
	)
	//3 5 7 9
	fmt.Println(a, b, c, d)

	const (
		v1,v2 =iota,iota*2
		v3,v4
		v5,v6
	)
	fmt.Println(v1,v2,v3,v4,v5,v6)

1.5 Printf使用整理(todo)

  • %T 变量类型
  • %d 数字
  • %v 详细内容

二、流程控制语句

if

语法格式

  1. 条件不需要括号
  2. 前花括号不能换行,格式严格
if condition{
    action1
}else if condition{
    action2
}else{
    action3
}

格式1

func ifSample() {
	const filename = "go.mod"
	data, err := ioutil.ReadFile(filename)
	if err == nil {
		fmt.Printf("%s", data)

	} else {
		fmt.Println(err)
	}
}

在if条件语句中声明变量,其生命周期只在if内部

func ifSample2() {
	const filename = "go.mod"
	//在if语句声明变量,声明周期只在if内部
	if data, err := ioutil.ReadFile(filename); err == nil {
		fmt.Printf("%s", data)

	} else {
		fmt.Println(err)
	}
}

switch

  1. 默认break,使用fallthrouch放行
  2. 每个条件必须跟冒号结尾

# 格式一,条件只能是变量

func switchSapmle(score int) string {

	var level string
	switch score {
	case 90:
		level = "A"
	case 100:
		level = "B"
	default:
		level = "C"
	}
	return level
}

# 格式二:条件可以是表达式
func switchSapmle2(score int) string {

	var level string
	switch {
	case score > 90:
		level = "A"
	case score > 60:
		level = "B"
	default:
		level = "C"
	}
	return level
}

循环语句

  1. golang只有for,没有while
  2. for不需要小括号
  3. for可以省略一切,如下

func forSample() {
	//省略小括号
	sum := 0
	for i := 0; i < 100; i++ {
		sum += i
	}
	fmt.Println(sum)
}

func forSample2(i int) {
	//省略起始值,等同于while(i<100)
	sum := 0
	for ; i < 100; i++ {
		sum += i
	}
	fmt.Println(sum)
}

func forSample3(i int) {
	//省略起始值和步增,等同于while(i<100)
	sum := 0
	for i < 100 {
		sum += i
		i++
	}
	fmt.Println(sum)
}

func forSample4() {
	//省略起始值和步增,等同于while(i<100)
	for {
		fmt.Println("deadLoop")
	}

}

三、函数

3.1 函数声明

  1. 名字在前 返回类型在后
  2. 没有函数的重载和重写
  3. go支持可变参数列表
  4. 支持func作为参数,支持匿名参数,不支持lambda表达式

单个返回值

func eval(a, b int, op string) int {

	switch op {
	case "+":
		return a + b
	case "-":
		return a - b
	case "*":
		return a * b
	case "/":
		return a / b
	default:
		panic("err input,pls check")

	}
}

//两个返回值,商和余数

func div(a, b int) (int, int) {
	return a / b, a % b
}

//两个返回值,商和余数,给返回值名字
func div1(a, b int) (q, r int) {
	return a / b, a % b
}

返回实名常量及作用域


func foo (a int,b int) (r,q int)  {
	fmt.Println("-------foo------")
	fmt.Println("a,b",a,b)

	//tip1: r,q也是函数foo的形式参数,初始值是0
	//tip2: r,q的作用域范围是函数{}内部
	r=2000
	q=100
	return
}

3.2 函数进阶

将error作为返回值

//通常我们用第二个返回值返回error
func eval1(a, b int, op string) (int, error) {

	switch op {
	case "+":
		return a + b, nil
	case "-":
		return a - b, nil
	case "*":
		return a * b, nil
	case "/":
		return a / b, nil
	default:
		return 0, fmt.Errorf("unsupport operation %s" + op)

	}
}
# 调用时判断后执行逻辑
	if result, err := eval1(3, 4, "&&"); err != nil {
		fmt.Println("error ")
	} else {

		fmt.Println("eval1 %s", result)
	}

函数作为参数和匿名函数

// 函数作为参数传递,根据传入的函数动态决定结果
func apply(op func(a, b int) int, a, b int) int {
	return op(a, b)
}

func pow(a, b int) int {
	return a*a + b*b
}

# 调用
//使用函数作为参数
	i := apply(pow, 3, 4)
	//直接使用匿名函数
	i2 := apply(func(a, b int) int {
		return a*b + 3
	}, 3, 4)
	fmt.Printf("函数作为参数pow %d", i)
	fmt.Printf("匿名函数 %d", i2)

3.3 init函数和main函数

  • main函数时程序的入口
  • init函数用于程序执行前pkg初始化函数(在main之前执行)

image

四、指针

对于第一次接触指针的同学,理解*int和&到本质层面才算掌握,否则会被指针概念绕晕。什么叫做理解到本质呢,一句话:看到语法就看到内存

4.1 值传递 or 引用传递

golang只有值传递一种方式,必须通过指针来实现引用传递。

4.2 *int&val

  • *int 代表指针类型,是一种数据类型,能且只能用来存放内存地址
  • &符号跟一个变量名代表获取该变量的内存地址。

func main()  {
	var a int =100
	changeValByValue(a)
	fmt.Println("atfer changeValByValue a=",a)
	changeValByPointer(&a)
	fmt.Println("atfer changeValByPointer a=",a)
}


func changeValByValue(v int){
	v=20
}

// *int代表存放int类型内存地址的指针类型

// 1. 当声明p是*int类型后,代表p仅能接收 整数的内存地址(可以通过&+变量获取)
// 2. p可以被修改为其他内存地址,比如从 &a->&b,但不能p=10,因为int类型不匹配*int
// 3. *p代表 p指向的内存地址,这里就是a的内存地址。当*p=30时,a指向的内存地址变为30.所以a的值发生变化
func changeValByPointer(p *int)  {
	fmt.Printf("type of *p is %T\n",p)  // *int
	fmt.Printf("type of p is %T\n",*p)  // int
	*p=30
}

通过文字理解比较困难,需要画出内存的变化一目了然

五、集合类型

5.1 数组

数据定义
func defArray() {
	//定义数组,默认赋值0
	var arr1 [5]int
	fmt.Println("init val of arr1 =",arr1)
	arr1=[5] int {1,3,4,5}
	fmt.Println(arr1)

	//定义数组并赋予初始值
	var arr2 = [3]int{1, 3, 4}
	arr3 := [3]int{13, 3, 4}
	fmt.Println(arr2, arr3)
	// 定义二位数组
	arr4 := [3][4]int{{1, 2, 3, 4}, {2, 4, 5, 5}, {3, 4, 5, 4}}
	fmt.Println(arr4)

	//不固定长度数组
	arr5 := [...]int{1, 3, 4}
	fmt.Println(arr5)
}
数据遍历

下划线可以用来你做参数占位,省略参数

func printArray() {

	arr := [3]int{13, 3, 4}
	for i := range arr {
		fmt.Println(arr[i])
	}

	fmt.Println("---------------")
	//打印index,value
	for i, v := range arr {
		fmt.Println(i, v)
	}
	fmt.Println("---------------")
	//打印value,_用于省略变量
	for _, v := range arr {
		fmt.Println(v)
	}
	fmt.Println("---------------")
	//不常用,for循环便利

	for i := 0; i < len(arr); i++ {
		fmt.Println(arr[i])
	}

}

数组值传递(go特殊)
  1. 数组是一种值类型,调用func f(arr[10] int)会拷贝数组
  2. 不同长度的数组是不同类型,长度3的数组不能接收长度2的数组。
  3. 数值也是值传递,需通过指针实现引用传递
//1.只能接受长度为3的数组
//2. 值传递而非引用长度
func valTransferArray(arr [3]int) {

	arr[0] = 10
	fmt.Println(arr)
}

//1.只能接受长度为3的数组
//2. 值传递而非引用长度
func pointerArray(arr *[3]int) {

	arr[0] = 10
	fmt.Println(arr)
}

func main() {
	arr := [3]int{3, 4, 5}
	valTransferArray(arr)
	//数组时值传递,所以数组并没有改变
	fmt.Printf("after %s", arr)

	pointerArray(&arr)
	fmt.Printf("afterpointer %s", arr)
}


使用指针可以实现引用传递

[10 4 5]
after [%!s(int=3) %!s(int=4) %!s(int=5)]&[10 4 5]
afterpointer [%!s(int=10) %!s(int=4) %!s(int=5)]

5.2 动态数组·切片 slice

slice和array很相似,但使用时有巨大差异

slice定义
  • 可以通过slice=nil判断是否为空切片
  • 不指定cap时,默认cap=len
func defSlice()  {
	//定义slice
	var s1 [] int
	fmt.Println(&s1)
	fmt.Println(s1==nil) //true ,可以用nil判断slice是否是空的
	fmt.Printf("len= %d  val=%v \n",len(s1),s1)

	//定义slice并赋初值,
	s2:=[]int{1,3,4}
	fmt.Printf("s2 leng=%d cap=%d val=%v \n",len(s2),cap(s2),s2)

	//定义slice并开辟空间(type,leng,cap),但不赋值
	s3:=make([]int,3,5)
	fmt.Printf("s3 leng=%d cap=%d val=%v \n",len(s3),cap(s3),s3)
}

切片切割

func sliceArray() {
	//半开半闭区间,包括开始,不包括结尾
	arr := [...]int{2, 3, 4, 5, 6, 7, 8, 9}
	fmt.Println(arr[2:4])
	fmt.Println(arr[:4])
	fmt.Println(arr[4:])
}

切片扩展

func appendSlice(){
	s1:=make([]int,3,5)
	fmt.Printf("s1 leng=%d cap=%d val=%v \n",len(s1),cap(s1),s1)
	s1=append(s1,1)
	fmt.Printf("s1 leng=%d cap=%d val=%v \n",len(s1),cap(s1),s1)
	s1=append(s1,2)
	fmt.Printf("s1 leng=%d cap=%d val=%v \n",len(s1),cap(s1),s1)

	//超出slice的cap时,会按照2*cap自动扩容
	s1=append(s1,3)
	fmt.Printf("s1 leng=%d cap=%d val=%v \n",len(s1),cap(s1),s1)


	s2:=append(s1,3,4)
	fmt.Printf("s2 leng=%d cap=%d val=%v \n",len(s2),cap(s2),s2)

}

增删改查

func testSlice2() {
	s2 := []int{23, 4, 3, 5}
	s3 := make([]int, 10, 32)

	//复制slice
	copy(s3, s2)
	printSlice(s2)
	printSlice(s3)

	//删除第N个元素
	s2 = append(s2[:1], s2[2:]...)
	printSlice(s2)

	//
}
slice和array关系

slice是数组的切片,对array的视图和引用,slice使用引用传递
注:slice是array的一个视图,本身没有数据

通过slice可以引用传递改变数组

	arr := [...]int{1, 3, 43}
	sliceTranArray(arr[:])
	fmt.Println("after", arr)

//使用slice传递数组
func sliceTranArray(array []int) {
	array[0] = 100
	fmt.Println(array)
}

切片原理(重要)

切片是array的视图,包含了三部分

  1. ptr切片的起点引用
  2. 切片长度
  3. cap扩展区域
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7f5CakVx-1648100093923)(E:/youdao/微信截图_20211128215021.png)]
func reSlice() {
	arr := [...]int{0,1, 2, 3, 4, 5, 6, 7}
	s1 := arr[2:6]
	s2 := s1[3:5]
	fmt.Println(s1)
	fmt.Println(s2)

}

结果

[2 3 4 5]
# s1指向的是arr的引用,引用中包含扩展区
# s2指向的也是arr的引用,3,5获取了扩展区内容
[5 6]

如果想slice添加的元素超过了cap的上线,系统就会创建新的数组,将原来的数组就会垃圾回收掉。

slice扩容机制

func printSlice(slice []int) {
	//打印slice的实际长度和扩展长度
	fmt.Println(len(slice), cap(slice))
}

func testSlice() {


	var s1 = []int{} //zero value is nil
	for i := 0; i < 100; i++ {

		s1 = append(s1, 2*i+1)
		printSlice(s1)
	}

	printSlice(s1)

}

slice达到16后是指数扩容的


1 1
2 2
3 4
4 4
5 8
6 8
7 8
8 8
9 16
10 16
11 16
12 16
13 16
。。。
60 64
61 64
62 64
63 64
64 64
65 128
66 128
67 128
。。。
100 128

Process finished with the exit code 0

5.3 Map(key-value)类型

  • map的key是无序的,可以通过slice转换排序
  • map使用hash表,key必须可以hash
  • slice,map,function以外的类型都可以作为key。自定义类型包括三者之一不能作为key
map定义

空map的长度都是0,map本身=nil可以判断map不包含元素

//方式1:仅声明一个map,map[key-type]value-type,但不赋值
	//tip:可以通过nil判断map是否包含元素
	var map1 map[string]string
	fmt.Println("map1==nil ",map1==nil)

	//方式2:make方式声明map,并制定初始size
	map2:=make(map[int]string,1)

	fmt.Println("size before",len(map2),map2) //0
	map2[0]="java"
	map2[1]="golang"
	map2[2]="python"
	fmt.Println("size after",len(map2),map2) //3

	//方式3:声明map同时赋初值
	map3:=map[string]string{
		"name":     "zhangsan",
		"location": "shagnhai",
	}
	fmt.Println(map3)


	//定义并赋值
	m := map[string]string{
		"name":     "zhangsan",
		"location": "shagnhai",
	}
	fmt.Println(m)
增删改查

使用for遍历,结果无序

	//key是无需的,hashmap结构
	for k, v := range m {ß
		fmt.Println(k, v)
	}

通过m[key]获取元素,可以通过下面方法判断是否存在

	fmt.Println(m["name"])
	//如果key不存在获取到zerovalue(这里是空字符串)
	fmt.Println(m["nameddd"])
	if s, ok := m["name"]; ok {
		fmt.Println(s)
	} else {
		fmt.Println("key not exist")
	}

delete(key) 删除元素
key不存在,删除无效,不报错

//删除元素
	fmt.Println("delete----")
	delete(m, "name")
	fmt.Println(m)

修改map内容,map作为参数可以理解为引用传递


//修改map
changeVal(m)
fmt.Println(m)
//map是引用传递,而非值传递
func changeVal(maptmp map[string]string){

	maptmp["New"]="new member"

}

完整代码

func crudMap()  {

	//定义并赋值
	m := map[string]string{
		"name":     "zhangsan",
		"location": "shagnhai",
	}
	fmt.Println(m)


	//遍历:key是无需的,hashmap结构
	for k, v := range m {
		fmt.Println(k, v)
	}

	//打印单个元素
	fmt.Println(m["name"])
	//如果key不存在获取到zerovalue(这里是空字符串)
	fmt.Println(m["nameddd"])
	if s, ok := m["nameddd"]; ok {
		fmt.Println(s)
	} else {
		fmt.Println("key not exist")
	}

	//删除元素
	fmt.Println("delete----")
	delete(m, "nme")
	fmt.Println(m)


	//修改map
	changeVal(m)
	fmt.Println(m)

}

5.4 rune

  • rune相当于golang的char类型
  • utf8.RunecountInString获取字符数
func runeTest() {

	str := "Yes,我爱中国!"  //utf-8编码,英语1个byte,汉子3bytes
	fmt.Println(len(str))
	for i, b := range []byte(str) {
		//长度为17位,等于len结果
		fmt.Printf(" (%d, %X) ", i, b)
	}

	fmt.Println("------------ch-------")
	for i, ch := range str {
		fmt.Printf(" (%d, %X) ", i, ch)
	}

	fmt.Println("-------------rune array")
	for i, r := range []rune(str) {
		fmt.Printf(" (%d, %X) ", i, r)
	}
}

结果
str转换成rune数组后,可以得到业务上的含义。这个过程是内部创建一个rune数组后转换的结果

17
 (0, 59)  (1, 65)  (2, 73)  (3, 2C)  (4, E6)  (5, 88)  (6, 91)  (7, E7)  (8, 88)  (9, B1)  (10, E4)  (11, B8)  (12, AD)  (13, E5)  (14, 9B)  (15, BD)  (16, 21) ------------ch-------
 (0, 59)  (1, 65)  (2, 73)  (3, 2C)  (4, 6211)  (7, 7231)  (10, 4E2D)  (13, 56FD)  (16, 21) -------------rune
 (0, 59)  (1, 65)  (2, 73)  (3, 2C)  (4, 6211)  (5, 7231)  (6, 4E2D)  (7, 56FD)  (8, 21) 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值