Go语言基础知识点

Go语言基础知识点

GO语言简绍

世界上已经有太多太多的编程语言了,为什么GO可以脱颖而出?

GO语言为并发而生,从底层原生支持并发,无须第三方库、开发者的编程技巧和开发经验。

GO的并发是基于goroutine的,类似于线程,但是并非线程,可以理解为一个虚拟线程,GO语言运行时会参与调度goroutine,并且将goroutine合理地分配到每个cpu中,最大限度的使用cpu性能,开启一个goroutine的消耗非常小(大约2kb),你可以轻松创建百万个goroutine。

goroutine特点:

  1. 它具有可增长的分段堆栈,这意味着它们只在需要时才会使用更多内存。
  2. 它的启动时间比线程快。
  3. 它原生支持利用channel安全地进行通信。
  4. 它共享数据结构时无需互斥锁。

学习Go语言的前景:

目前Go语言已经⼴泛应用于人工智能、云计算开发、容器虚拟化、⼤数据开发、数据分析及科学计算、运维开发、爬虫开发、游戏开发等领域。

Go语言简单易学,天生支持并发,完美契合当下高并发的互联网生态。Go语言的岗位需求持续高涨,目前的Go程序员数量少,待遇好。

抓住趋势,要学会做一个领跑者而不是跟随者。

国内Go语言的需求潜力巨大,目前无论是国内大厂还是新兴互联网公司基本上都会有Go语言的岗位需求。

GO语言基础之常量和变量

变量声明

var 变量名 变量类型
例子:
var name string 
var age int
var isOk bool
批量声明:
var(
    a string 
    b int
    c bool
    d float32
)
变量初始化:
var 变量名 类型 = 表达式
例子:
var name string = "段乐乐"
var  age int = 22
var name,age = "段乐乐",22

类型辅导:(省略了变量的类型)
var name = "段乐乐"
var age = 22

短变量声明(在函数内部可以使用)
a := 22
b := "段乐乐"
匿名变量(想要忽略某个值时可以使用):
func class(string ,int){
    return "段乐乐",22
}
func main(){
    x,_ := class()
    fmt.Println(x)
}
注:匿名变量不会占用命名空间,不会分配内存,也不存在重复声明。

常量

常量时恒定不变的值,多用于程序运行时不会改变的值。

格式:
const pai = 3.14
const name = "段乐乐"
在整个程序运行期间它们的值不会发生改变。
多变量声明:
const(
   p = 3.14
   name = "段乐乐"
)
const同时声明多个常量时,如果忽略了值则表示和上面一行的值相同。
例子:
const(
	a = 100
    b
    c
)

iota

iota是go语言的常量计数器,只能在常量的表达式中使用。

otaconst关键字出现时将被重置为0。const中每新增一行常量声明iota计数一次。(定义枚举时很常用)

const(
	a = iota  //0
    b         //1
    c         //2
    d         //3
)
几个常见的iota例子:
1.使用_跳过某些值
const(
	a = iota  //0
    b         //1
    _
    d         //3
)

2.iota声明中间插队
const(
	a = iota //0
    b = 100 //100
    c = iota //2
    d        //3
)
const f = iota //0

3.定义数量级(这里的<<表示左移操作,1<<10表示将1的二进制表示向左移10位,也就是由1变成了10000000000,也就是十进制的1024。同理2<<2表示将2的二进制表示向左移2位,也就是由10变成了1000,也就是十进制的8。)
const (
		_  = iota
		a = 1 << (10 * iota)
		b = 1 << (10 * iota)
		c = 1 << (10 * iota)
		d = 1 << (10 * iota)
		e = 1 << (10 * iota)
	)

4.多个iota定义在一行
const (
	a,b = iota+1,iota +2 //1,2
    c,d       //2,3
    e,f       //3,4
)

GO语言基本之数据类型

基本数据类型

整型

整型分为以下两个大类: 按长度分为:int8、int16、int32、int64 对应的无符号整型:uint8、uint16、uint32、uint64

其中,uint8就是我们熟知的byte型,int16对应C语言中的short型,int64对应C语言中的long型。

//十进制
var a int = 10
fmt.Printf("5d",a) //10
fmt.Printf("%b",a) //1010 %b表示二进制

//八进制  以0开头
var b int = 077
fmt.Printf("%o",b)  //77

//十六进制 以ox开头
var c = oxff
fmt.Printf("%x",c) //ff

浮点型

go语言支持两种浮点型数:float32float64

打印浮点数的时候使用%f
var a = 10.0
fmt.Printf("%f",a)

复数

complex64complex128
复数有实部和虚部,complex64的实部和虚部为32位,complex128的实部和虚部为64位。
var a = 1+2i
var b = 1+3i
fmt.Println(a)
fmt.Println(b)

布尔值

Go语言中以bool类型进行声明布尔型数据,布尔型数据只有true(真)和false(假)两个值。
注:
1.布尔类型的变量声明默认值为false
2.GO语言中不允许将整型强制转换为布尔型
3.布尔型无法参与数值运算,也无法进行其他类型转换

字符串

Go语言中的字符串以原生数据类型出现
a := "段乐乐"
转义符号:
\r 返回行首 
\n 换行符
\t 制表符
\' 单引号
\" 双引号
\\ 反斜杠

应用:
定义一个多行字符串时,使用反引号'
a := '你
 好
'
字符串的常用操作:
len(str)  求长度
+或fmt.Sprintf 拼接字符串
strings.Split  分割
strings.contains 判断是否包含
strings.HasPrefix,strings.HasSuffix 前缀后缀判断
strings.Index(),Strings.LastIndex()   字串出现的位置
strings.Join(a[]string,sep string)  join操作

byte和true类型

组成每个字符串的元素叫做“字符”,可以通过遍历或者单个获取字符串元素获得字符。 字符用单引号(’)包裹起来
var a = '段'
var b = '乐'
go语言的字符有以下两种:
1.uint8类型,或者叫 byte 型,代表了ASCII码的一个字符。
2.rune类型,代表一个 UTF-8字符
当需要处理中文、日文或者其他复合字符时,则需要用到rune类型。rune类型实际是一个int32
例子:
str := "你好,我想认识你"
for _,k := range str{
    fmt.Println(string(k))
}

修改字符串:
要修改字符串,需要先将其转换成[]rune[]byte,完成后再转换为string。
a := "wi好,我想认识你"
str := []byte(a)
str[0] = 'n'
fmt.Println(string(str))

类型转换

Go语言中只有强制类型转换,没有隐式类型转换。

T(表达式)
T 表示要转换的类型
例子:
计算直角三角形的斜边长时使用math包的Sqrt()函数,该函数接收的是float64类型的参数
var a ,b =3,4
c := int(math.Sqrt(float64(a*a+b*b))) //float64类型  float32不可以
fmt.Println(c)

GO语言基础之运算符

Go 语言内置的运算符有:

  1. 算术运算符
  2. 关系运算符
  3. 逻辑运算符
  4. 位运算符
  5. 赋值运算符
算术运算符:+ - * / %  
关系运算符: === <  >  <=  >= 
逻辑运算符:&& || !
这几个就不说了 这跟其特如Java c一样的
位运算符:
位运算符对整数在内存中的二进制位进行操作。
& 参与运算的两数各对应的二进位相与
| 参与运算的两数各对应的二进位相或
^ 参与运算的两数各对应的二进位相异
<< 左移n位就是乘以2的n次方 “a<<b”是把a的各二进位全部左移b位,高位丢弃,低位补0
>> 	右移n位就是除以2的n次方。“a>>b”是把a的各二进位全部右移b位
赋值运算符
=  +=  -=  *= /+  %=  <<=   >>= &= |=   ^=
这些简单根据上面的理解就可以

GO语言基础之流程控制

Go语言中最常用的流程控制有iffor,而switchgoto主要是为了简化代码、降低重复代码而生的结构,属于扩展类的流程控制。

if else(分支结构)

if 表达式一{
    
}else if 表达式二{
    
}else {
    
}
Go语言规定与if匹配的左括号{必须与if和表达式放在同一行,{放在其他位置会触发编译错误。 同理,与else匹配的{也必须与else写在同一行,else也必须与上一个ifelse if右边的大括号在同一行。
也就是说 按照上面我写的格式。
if的特殊写法 可以在 if 表达式之前添加一个执行语句,再根据变量值进行判断
if a := 10; a>59{
    
}else {
    
}

for循环结构

Go 语言中的所有循环类型均可以使用for关键字来完成

for循环的基本格式如下:
for 初始语句;条件语句;结束语句{
    循环体语句
}
例如:
for i := 0; i < 10; i++ {
		fmt.Println(i)
	}

另一个写法
i := 0
	for i < 10 {
		fmt.Println(i)
		i++
	}
可以使用breakgoto,return,panic语句强制退出
Go语言中可以使用for range遍历数组、切片、字符串、map 及通道(channel)。 通过for range遍历的返回值有以下规律:
1.数组、切片、字符串返回索引和值。
2.map返回键和值。
3.通道(channel)只返回通道内的值。

switch case

例子如下:
finger := 3
switch finger{
case 1:
    fmt.Println(1)
case 2:
    fmt.Println(2)
    case 3:
    fmt.Println(3)
    default :
    fmt.Println("其他")
}
Go语言规定每个switch只能有一个default分支。

一个分支可以有多个值,多个case值中间使用英文逗号分隔。
例子如下:
finger := 3
switch finger{
case 1789:
    fmt.Println(1)
case 2:
    fmt.Println(2)
case 3:
    fmt.Println(3)
default :
    fmt.Println("其他")
}

还可以使用表达式
age := 30 
switch age{
    case age >30:
        fmt.Println("大于30")
    defaul:
   	    fmt.Println("没有大于30")
}

fallthrough语法可以执行满足条件的case的下一个case,是为了兼容C语言中的case设计的。
finger := 3
switch finger{
case 1789:
    fmt.Println(1)
    fallthrough
case 2:
    fmt.Println(2)
    fallthrough
case 3:
    fmt.Println(3)
default :
    fmt.Println("其他")
}

goto跳转到指定标签

goto语句通过标签进行代码间的无条件跳转。goto语句可以在快速跳出循环、避免重复退出上有一定的帮助
for j := 0; j < 10; j++ {
		if j == 2 {
				// 设置退出标签
			goto breakTag
		}
}
breakTag: fmt.Println("结束了for循环")

break(跳出循环)

break语句可以结束forswitchselect的代码块。
break语句还可以在语句后面添加标签,表示退出某个标签对应的代码块,标签要求必须定义在对应的forswitchselect的代码块上。 举个例子:
BREAKDEMO1:
	for i := 0; i < 10; i++ {
		for j := 0; j < 10; j++ {
			if j == 2 {
				break BREAKDEMO1
			}
			fmt.Printf("%v-%v\n", i, j)
		}
	}

continue(继续下次循环)

continue语句可以结束当前循环,开始下一次的循环迭代过程,仅限在for循环内使用。

在 continue语句后添加标签时,表示开始标签对应的循环。例如:
for i :=0;i<5;i++{
    if i==2 {
        continue
    }
    fmt.Println(i)
}

GO语言基础之数组

Array数组

数组是同一种数据类型元素的集合。 在Go语言中,数组从声明时就确定,使用时可以修改数组成员,但是数组大小不可变化。 基本语法:

数组定义
var 数组变量名 [数组元素]T
例如:
var a [3]int

数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:len-1,访问越界(下标在合法范围之外),则触发访问越界,会panic
数组的初始化
例如:
1. var test1 [3]int
2. var test2 [3]{1,2,3}
3. var test3 [...]int{1,2,3}
4. var test4 [...]int{1:2,2:3}  可以使用指定索引值的方式来初始化数组
数组的遍历
1.
var a = [...]int{1,2,3,4}
for i:= 0;i<len(a);i++{
    fmt.Println(a[i])
}
2.
var a = [...]int{1,2,3,4}
for _,i := range a{
    fmt.Println(i) 
}
多维数组:
a := [3][2]int{
    {1,2},
    {1,3},
    {1,4},
}
数组遍历:
for _,v := range a{
    for _,value := range v{
        fmt.Println(value)
    }
}
注: 多维数组只有第一层可以使用...来让编译器推导数组长度
例如:
a := [...][2]{
    {1,2},
    {2,3},
}
数组是值类型
数组是值类型,赋值和传参会复制整个数组。因此改变副本的值,不会改变本身的值
func modifyArray(x [3]int) {
	x[0] = 100
}

func modifyArray2(x [3][2]int) {
	x[2][0] = 100
}
func main() {
	a := [3]int{10, 20, 30}
	modifyArray(a) //在modify中修改的是a的副本x
	fmt.Println(a) //[10 20 30]
	b := [3][2]int{
		{1, 1},
		{1, 1},
		{1, 1},
	}
	modifyArray2(b) //在modify中修改的是b的副本x
	fmt.Println(b)  //[[1 1] [1 1] [1 1]]
}

GO基础之切片

因为数组的长度是固定的并且数组长度属于类型的一部分,所以数组有很多的局限性。

切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。

切片的声明:
var name[]T
name:变量名
T:数据类型
例如: var a[]string
切片的长度和容量:
len()函数求长度,cap()函数求切片容量

切片的底层就是一个数组,所以我们可以基于数组通过切片表达式得到切片。 切片表达式中的low和high表示一个索引范围(左包含,右不包含),也就是下面代码中从数组a中选出1<=索引值<4的元素组成切片s,得到的切片长度=high-low,容量等于得到的切片的底层数组的容量。
例如:
a := [5]int{1, 2, 3, 4, 5}
s := a[1:3]  // s := a[low:high] len(s)=2 cap(s)=4
	fmt.Println(s) 
为了方便:
a[2:]  // 等同于 a[2:len(a)]
a[:3]  // 等同于 a[0:3]
a[:]   // 等同于 a[0:len(a)]
使用make()函数构造切片:
make([]元素类型,元素数量,切片容量)
a := make([]int,3,6)
fmt.Println(a)
切片本质:
切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针,切片的长度,切片的容量
数组:a := [8]int{0,1,2,3,4,5,6,7}
切片:s := a[:5]
判断切片是否为空:
用len()==0  不是使用s == nil
切片之间不能比较,一个nil值的切片没有底层数组,一个nil值的切片的长度和容量都是0,但是一个长度和容量都是0的切片一定是nil
例子:
var a []int      //len(s1)=0;cap(s1)=0;s1==nil
b := []int{}     //len(s1)=0;cap(s1)=0;s1!=nil
c := make([]int,0)     //len(s1)=0;cap(s1)=0;s1!=nil
切片的赋值拷贝
s1 := make([]int,3)
s2 := s1
s2[0] = 1
fmt.Println(s1) //{1,0,0}
fmt.Println(s2)  //{1,0,0}
切片的遍历
s := []int{1, 3, 5}
1.
	for i := 0; i < len(s); i++ {
		fmt.Println(i, s[i])
	}
2.
	for index, value := range s {
		fmt.Println(index, value)
	}
append()方法为切片添加元素
var s []int
s  := appedn(s,1)
s = append(s,2,3,4)
s2 := []int{5,6,7}
s = append(s,s2...)
切片的复制问题
a := []int{1,2,3,4,5}
b := a
fmt.println(a) //[1,2,3,4,5]
fmt.Println(b) //[1,2,3,4,5]
b[0]=100
fmt.println(a) //[100,2,3,4,5]
fmt.Println(b) //[100,2,3,4,5]
由于切片是引用类型,所以a和b都指向一块内存地址。

GO语言中的copy()函数可以将一个切片的数据复制到另一个切片空间中
例如:
a := []int{1,2,3,4,5}
c = make([]int,5,5)
copy(c,a)
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(c) //[1 2 3 4 5]
c[0] = 1000
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(c) //[1000 2 3 4 5]
切片中删除元素
a := []int{1,2,3,4,5,6}
a = append(a[:2],a[3:]...) // 1,2,4,5,6

GO基础之map

map定义:
map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用。
make(map[KeyType]ValueType, cap)
map基本使用:
s := make(map[string]int,3)
s["1"]=1
fmt.Println(s) //map[1:1]
fmt.Println(s["1"]) // 1

user := map[int]int{
    1 :1,
    2:2,
}
map判断某个键是否存在:
value, ok := map[key]
例子:
s := make(map[int]int)
s[1]=1
v,ok := s[1]
if ok{
    fmt.Println(v)
}else{
    fmt.Println("查不到")
}
map 的遍历
a := make(map[int]int)
a[1] = 1
a[2] = 2
for k := range a{
    fmt.Println(k)
}:遍历map时的元素与添加键值对的顺序无关
map的删除 delete()
delete(map,key)
map:表示要删除键值对的map
key:表示要删除的键值对的键
例子:
a := make(map[int]int)
a[1] =1
a[2] =2
fmt.Println(a)  //map[1:1 2:2]
delete(a,1)
fmt.Println(a) //map[2:2]
元素为map类型的切片
var a = make([]map[int]int,3)
a[0] = make(map[int]int,10)
a[0][1]=1
值为切片类型的map
var a = make(map[int][]string,3)
key := 1
value,ok := a[key]
if !ok {
		value = make([]string, 0, 2)
	}
value = append(value, "北京", "上海")
	sliceMap[key] = value
	fmt.Println(sliceMap)
这是程序运行的结果:
map[]
after init           
map[中国:[北京 上海]]

GO语言基础之函数

函数定义:
func 函数名(参数)  返回值{
	函数体
}
例子:
func sun(x int,y int) int{      ==    func sun(x,y int) int{
    return x+y							   return x+y							
}                                      }
func say(){
    fmt.Println("你好吖!")
}
函数的调用:
say()
s := sun(10,20)
可变参数
1.
func sum(x...int) int{
    sum := 0
    for _,v := rage x{
        sum += v
    }
    return sum
}
调用的话:
s1 := sum(10)
s2 := sum(19,2,3,3)
2.
func sum(x int,y...int) int{
    sum := x
    for _,v := rage x{
        sum += v
    }
    return sum
}
调用的话:
s1 := sum(10)
s2 := sum(19,2,3,3)
函数返回值
func calc(x, y int) (sum, sub int) {
	sum = x + y
	sub = x - y
	return
}
了解函数的变量作用域:
全局变量: 定义在函数体外的
局部变量:函数内定义的变量无法在该函数外使用  如果局部变量和全局变量重名,优先访问局部变量。(就近原则)
匿名函数和闭包:
匿名函数:
1.匿名函数保存到变量中
a := func(x,y int) int{
    return x+y
}
a(1,2)
2.自执行函数
a := func(x,y int) int{
    return x+y
}()
闭包:
闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+引用环境。 首先我们来看一个例子:
变量f是一个函数并且它引用了其外部作用域中的x变量,此时f就是一个闭包。 在f的生命周期内,变量x也一直有效。
func adder() func(int) int {
	var x int
	return func(y int) int {
		x += y
		return x
	}
}
func main() {
	var f = adder()
	fmt.Println(f(10)) //10
	fmt.Println(f(20)) //30
	fmt.Println(f(30)) //60

	f1 := adder()
	fmt.Println(f1(40)) //40
	fmt.Println(f1(50)) //90
}
defer语句
GO语言的defer语句会将其后面跟随的语句进行延迟处理,在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行。
例子:
func main() {
	fmt.Println("start")
	defer fmt.Println(1)
	defer fmt.Println(2)
	defer fmt.Println(3)
	fmt.Println("end")
}
结果:
start
end
3
2
1
由于defer语句延迟调用的特性,所以defer语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、解锁及记录时间等
defer注册要延迟执行的函数时该函数所有的参数都需要确定其值
下面是一个面试题目:
func calc(index string, a, b int) int {
	ret := a + b
	fmt.Println(index, a, b, ret)
	return ret
}

func main() {
	x := 1
	y := 2
	defer calc("AA", x, calc("A", x, y))
	x = 10
	defer calc("BB", x, calc("B", x, y))
	y = 20
}
结果就是:
B 10 2 12  
BB 10 12 22
AA 1 3 4   
一些内置函数简绍:
close(): 用来关闭channel
len : 用来求长度
new :  用来分配内存,主要用来分配值类型
make : 用来分配内存,主要用来分配引用类型
append : 用来追加元素到数组,slice中
panice 和 recover : 用来做错误处理

GO语言基础之指针

要搞明白Go语言中的指针需要先知道3个概念:指针地址、指针类型和指针取值。
GO语言的指针 :我们只需要记住两个符号:&(取地址)和*(根据地址取值)。
func main() {
	a := 10
	b := &a
	fmt.Printf("a:%d ptr:%p\n", a, &a) // a:10 ptr:0xc00001a078
	fmt.Printf("b:%p type:%T\n", b, b) // b:0xc00001a078 type:*int
	fmt.Println(&b)                    // 0xc00000e018
}
另一个例子:
func main() {
	//指针取值
	a := 10
	b := &a // 取变量a的地址,将指针保存到b中
	fmt.Printf("type of b:%T\n", b)
	c := *b // 指针取值(根据指针去内存取值)
	fmt.Printf("type of c:%T\n", c)
	fmt.Printf("value of c:%v\n", c)
}
结果:
type of b:*int
type of c:int
value of c:1
对变量进行取地址(&)操作,可以获得这个变量的指针变量。
指针变量的值是指针地址。
对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。
例子:
func modify1(x int) {
	x = 100
}

func modify2(x *int) {
	*x = 100
}

func main() {
	a := 10
	modify1(a)
	fmt.Println(a) // 10
	modify2(&a)
	fmt.Println(a) // 100
}
newmake的区别:
二者都是用来做内存分配的。
make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;
而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针。

GO语言基础之结构体

自定义类型:
type 结构体名 类型
类型别名与类型定义表面上看只有一个等号的差异,我们通过下面的这段代码来理解它们之间的区别。
//类型定义
type NewInt int

//类型别名
type MyInt = int

func main() {
	var a NewInt
	var b MyInt
	
	fmt.Printf("type of a:%T\n", a) //type of a:main.NewInt
	fmt.Printf("type of b:%T\n", b) //type of b:int

结构体的定义:
使用typestruct关键字来定义结构体,具体代码格式如下:
type 类型名 struct {
    字段名 字段类型
    字段名 字段类型
    …
}
例子:
type person struct {
	name string
	city string
	age  int8
}

type person1 struct {
	name, city string
	age        int8
}

结构体实例化:
type person struct {
	name string
	city string
	age  int8
}

func main() {
	var p1 person
	p1.name = "沙河娜扎"
	p1.city = "北京"
	p1.age = 18
	fmt.Printf("p1=%v\n", p1)  //p1={沙河娜扎 北京 18}
	fmt.Printf("p1=%#v\n", p1) //p1=main.person{name:"沙河娜扎", city:"北京", age:18}
}
指针类型结构体:
我们还可以通过使用new关键字对结构体进行实例化,得到的是结构体的地址。
var p2 = new(person)
fmt.Printf("%T\n", p2)     //*main.person
fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"", city:"", age:0}
从打印的结果中我们可以看出p2是一个结构体指针。

需要注意的是在Go语言中支持对结构体指针直接使用.来访问结构体的成员。
var p2 = new(person)
p2.name = "小王子"
p2.age = 28
p2.city = "上海"
fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"小王子", city:"上海", age:28}&对结构体进行取地址操作相当于对该结构体类型进行了一次new实例化操作
p3 := &person{}

结构体的初始化:
没有初始化的结构体,其成员变量都是对应其类型的零值。
type person struct {
	name string
	city string
	age  int8
}

func main() {
	var p4 person
	fmt.Printf("p4=%#v\n", p4) //p4=main.person{name:"", city:"", age:0}
}

使用键值对初始化:
p5 := person{
	name: "小王子",
	city: "北京",
	age:  18,
}
fmt.Printf("p5=%#v\n", p5) //p5=main.person{name:"小王子", city:"北京", age:18}

也可以对结构体指针进行键值对初始化,例如:

p6 := &person{
	name: "小王子",
	city: "北京",
	age:  18,
}
fmt.Printf("p6=%#v\n", p6) //p6=&main.person{name:"小王子", city:"北京", age:18}
某些字段可以不初始化,默认值就是该类型的零值

结构体内存:
结构体占用一块连续的内存
type a struct{
    h int
    b int
    c int
    d int
}
n := a{
    1,2,3,4,
}
fmt.Println(&n.h)  
fmt.Println(&n.b)
fmt.Println(&n.c)
fmt.Println(&n.d)
输出为:
n.a 0xc0000a0060
n.b 0xc0000a0061
n.c 0xc0000a0062
n.d 0xc0000a0063

空结构体:
空结构体不占用内存
var v struct{}
fmt.Println(unsafe.Sizeof(v))  // 0
构造函数:
GO语言中没有构造函数,我们可以自己实现
例子:
func newPerson(name,city string,age int) *person{
    return &person{
        name : name,
        city : city,
        age : age,
    }
}
调用构造函数:
p := newPerson("ni","shanghai".12)
fmt.Println(p)
方法和接收者:
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
    函数体
}
接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名称首字母的小写,而不是self、this之类的命名。例如,Person类型的接收者变量应该命名为 p,Connector类型的接收者变量应该命名为c等。
接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型。
方法名、参数列表、返回参数:具体格式与函数定义相同。
例子:
type Person struct{
    name string
    age int
}
func NewPerson(name string,age int) *Person{
    return &Person{
        name : name,
        age : age,
    }
}
func (p Person) Dream(){
    fmt.Println(p.name)
}
func main() {
	p1 := NewPerson("小王子", 25)
	p1.Dream()
}
注:方法与函数的区别是,函数不属于任何类型,方法属于特定的类型。
指针类型的接收者:
指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式就十分接近于其他语言中面向对象中的this或者self。 例如我们为Person添加一个SetAge方法,来修改实例变量的年龄。
// SetAge 设置p的年龄
// 使用指针接收者
func (p *Person) SetAge(newAge int8) {
	p.age = newAge
}
调用该方法:

func main() {
	p1 := NewPerson("小王子", 25)
	fmt.Println(p1.age) // 25
	p1.SetAge(30)
	fmt.Println(p1.age) // 30
}

值类型的接收者:
当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。
// SetAge2 设置p的年龄
// 使用值接收者
func (p Person) SetAge2(newAge int8) {
	p.age = newAge
}

func main() {
	p1 := NewPerson("小王子", 25)
	p1.Dream()
	fmt.Println(p1.age) // 25
	p1.SetAge2(30) // (*p1).SetAge2(30)
	fmt.Println(p1.age) // 25
}

什么时候应该使用指针类型接收者: 
需要修改接收者中的值
接收者是拷贝代价比较大的大对象
保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者
这里匿名字段的说法并不代表没有字段名,而是默认会采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个结构体的匿名字段:
结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段。

//Person 结构体Person类型
type Person struct {
	string
	int
}

func main() {
	p1 := Person{
		"小王子",
		18,
	}
	fmt.Printf("%#v\n", p1)        //main.Person{string:"北京", int:18}
	fmt.Println(p1.string, p1.int) //北京 18
}
这里匿名字段的说法并不代表没有字段名,而是默认会采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个
嵌套结构体:
//Address 地址结构体
type Address struct {
	Province string
	City     string
}

//User 用户结构体
type User struct {
	Name    string
	Gender  string
	Address Address   //这里就是嵌套
}


GO语言基础之包

包依赖与包管理:

在工程化的Go语言开发项目中,Go语言的源码复用是建立在包(package)基础之上的。

Go语言中支持模块化的开发理念,在Go语言中使用包(package)来支持代码模块化和代码复用。一个包是由一个或多个Go源码文件(.go结尾的文件)组成,是一种高级的代码复用方案,Go语言为我们提供了很多内置包,如fmtosio等。

例子:
package main

import "fmt"

func main(){
  fmt.Println("Hello world!")
}
包的引入
要在当前包中使用另外一个包的内容就需要使用import关键字引入这个包,并且import语句通常放在文件的开头,package声明语句的下方。完整的引入声明语句格式如下:

import importname "path/to/package"
其中:

importname:引入的包名,通常都省略。默认值为引入包的包名。

path/to/package:引入包的路径名称,必须使用双引号包裹起来。

Go语言中禁止循环导入包。


当引入的多个包中存在相同的包名或者想自行为某个引入的包设置一个新包名时,都需要通过importname指定一个在当前文件中使用的新包名。例如,在引入fmt包时为其指定一个新包名f
例子:
import f "fmt"
这样在当前这个文件中就可以通过使用f来调用fmt包中的函数了。
init 初始化函数
在每一个Go源文件中,都可以定义任意个如下格式的特殊函数:
func init(){
    
}
这种特殊的函数不接收任何参数也没有任何返回值,我们也不能在代码中主动调用它。当程序启动的时候,init函数会按照它们声明的顺序自动执行。

每一个包的初始化是先从初始化包级别变量开始的。例如从下面的示例中我们就可以看出包级别变量的初始化会先于init初始化函数。
例子:
package main

import "fmt"

var x int8 = 10

const pi = 3.14

func init() {
  fmt.Println("x:", x)
	fmt.Println("pi:", pi)
	sayHi()
}

func sayHi() {
	fmt.Println("Hello World!")
}

func main() {
	fmt.Println("你好,世界!")
}

结果是:
x: 10
pi: 3.14
Hello World!
你好,世界
go module
在Go语言的早期版本中,我们编写Go项目代码时所依赖的所有第三方包都需要保存在GOPATH这个目录下面。这样的依赖管理方式存在一个致命的缺陷,那就是不支持版本管理,同一个依赖包只能存在一个版本的代码。可是我们本地的多个项目完全可能分别依赖同一个第三方包的不同版本。
o module 是 Go1.11 版本发布的依赖管理方案,从 Go1.14 版本开始推荐在生产环境使用,于Go1.16版本默认开启。Go module 提供了以下命令供我们使用:

go module相关命令:
go mod init	初始化项目依赖,生成go.mod文件
go mod download	根据go.mod文件下载依赖
go mod tidy	比对项目文件中引入的依赖与go.mod进行比对
go mod graph	输出依赖关系图
go mod edit	编辑go.mod文件
go mod vendor	将项目的所有依赖导出至vendor目录
go mod verify	检验一个依赖包是否被篡改过
go mod why	解释为什么需要某个依赖
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值