数组

【数组的概念】数组是同一种数据类型元素的集合。

【数组的声明与初始化】 声明之后,不可改变类型,不可改变长度。同类型&&同长度数组之间才可以相互赋值

 

// 数组初始化的几种方法
var num = [...]int{1, 2, 3} // [...]表示让编译器自己推断数组长数 var a = [9]int{1, 2, 3, 4, 5, 6, 7, 8, 9} var b [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} c := [8]int{1, 2, 3, 4, 5, 6, 7, 8} // 初始化不赋值 var d = [9]int{} fmt.Println(d) // [0 0 0 0 0 0 0 0 0] // 数组元素的访问 fmt.Println(a[0]) // 1 // 数组的长度 fmt.Println(len(b)) // 10 // 数组的遍历 for k, v := range c { fmt.Println(k, v) }

【多维数组】

a := [3][4]int{  
	{0, 1, 2, 3} ,   /*  第一行索引为 0 */
	{4, 5, 6, 7} ,   /*  第二行索引为 1 */
	{8, 9, 10, 11},   /* 第三行索引为 2 */
}

fmt.Println(a[1][2])	// 索引为1的第3个元素  

数组是值类型,赋值会复制整个数组。因此改变副本的值,不会改变本身的值

package main

import "fmt"

func main() {
	var a = [9]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
	var b [9]int
	b = a
	a[1] = 5
	fmt.Println(a)	// [1 5 3 4 5 6 7 8 9]
	fmt.Println(b)	// [1 2 3 4 5 6 7 8 9]
}

  

 

切片

【切片的概念】(Slice)是一个拥有相同类型元素的可变长度的序列

 

【切片的声明】

var a []string              //声明一个字符串切片
var b = []int{}             //声明一个整型切片并初始化
var c = []bool{false, true} //声明一个布尔切片并初始化
var d = []bool{false, true} //声明一个布尔切片并初始化

  

【切片的初始化】

//创建一个容量cap为8,长度len为5的零值切片,值为[0 0 0 0 0]
var s1 []int = make([]int, 5, 8)
 
//创建一个容量和长度都为8的零值切片,值为[0 0 0 0 0 0 0 0]
var s2 []int = make([]int, 8)
 
//也可以这样创建零值切片,参数从左到右依次是类型,Len,cap
var s1 = make([]int, 5, 8)
s2 := make([]int, 8)
 
//这样创建的切片是满容的
var s []int = []int{1,2,3,4,5}  // cap和len都为5
 
//创建空切片,他们值均为[] cap为0 len也为0
var s1 []int                   
var s2 []int = []int{}
var s3 []int = make([]int, 0)

切片之间直接赋值之后,修改其中一个切片的元素的值,另一个切片相对应元素的值会随之改变

切片之间用copy()赋值后,修改其中一个切片元素的值,另一个切片相对应元素的值不会发生改变

 

【切片的基础操作】

package main
 
func main() {
    var s1 []int
    s2 := []int{14,15}
    for i := 0; i < 10; i++ {
        s1 = append(s1, i)
    }
 
    // 添加一个元素元素
    s1 = append(s1, 10)
 
    // 添加多个元素元素
    s1 = append(s1, 11, 12, 13)
 
    // 添加另一个切片的元素(后面加…)
    s1 = append(s1, s2...)
 
    // 删除索引最小的元素
    _ = s1[1:]
 
    // 删除索引最大的元素
    _ = s1[:1]
 
    // 删除 (非最大最小 && 索引为index)的元素
    index := 4
    _ = append(s1[:index], s1[index+1:]...)
}

切片append之后赋值给一个新的变量,不能赋值给_,切片append追加之后如果超出了原切片的容量,会变成一个新的切片,不再和原切片共享同一个底层数组,原切片改变元素新切片对应元素不变。

切片的扩容要用append,不能用赋值的方法扩容,否则会触发panic中断程序

func main() {
    var s1 []int
    s1[0] = 1   // 此处会触发panic
}

当比较短的切片扩容时,系统会多分配 100% 的空间
但切片长度超过1024时,扩容策略调整为多分配 25% 的空间

 

【切片之后再切片】

 

package main

import (
    "fmt"
)

func main() {
    a := [5]int{1, 2, 3, 4, 5}
    s := a[1:3] // s := a[low:high]
    //s:[2 3]
    fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s))
    s2 := s[3:4] // 索引的上限是cap(s)而不是len(s)
    fmt.Printf("s2:%v len(s2):%v cap(s2):%v\n", s2, len(s2), cap(s2))
}

 结果:

s:[2 3] len(s):2 cap(s):4
s2:[5] len(s2):1 cap(s2):1

 

【len()和cap】切片拥有自己的长度和容量,我们可以通过使用内置的len()函数求长度,使用内置的cap()函数求切片的容量。

 

【nil切片和空切片】

package main

import "fmt"

func main(){
	// nil切片
	var a []int
	fmt.Printf("a的指针地址是%p\n", a)
	if a == nil{
		fmt.Println("a is nil")
	}else{
		fmt.Println("a is not nil")
	}

	// 空切片
	b:=make([]int,0)
	fmt.Printf("b的指针地址是%p\n", b)
	if b == nil{
		fmt.Println("b is nil")
	}else{
		fmt.Println("b is not nil")
	}
}

结果:

a的指针地址是0x0
a is nil
b的指针地址是0x5a6d68
b is not nil

  

【append 和 copy】append见↑切片的基础操作

package main

import "fmt"

func main(){
	// 切片赋值了之后修改任意一个切片的元素,另一个也会变
	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] = 1000
	fmt.Println(a) //[1000 2 3 4 5]
	fmt.Println(b) //[1000 2 3 4 5]

	// copy()复制切片,值拷贝值,不赋值底层数组
	d := []int{1, 2, 3, 4, 5}
	c := make([]int, 5, 5)
	copy(c, d)     //使用copy()函数将切片d中的元素复制到切片c
	fmt.Println(d) //[1 2 3 4 5]
	fmt.Println(c) //[1 2 3 4 5]
	c[0] = 1000
	fmt.Println(d) //[1 2 3 4 5]
	fmt.Println(c) //[1000 2 3 4 5]
}

  

Map

 

【Map的概念】map是一种无序的基于key-value的数据结构

 

【Map的定义】

var testMap map[string]int // 定义map
fmt.Printf("%p", testMap)  // 0x0
testMap["第一"] = 1         // 还未给Map分配内存,此处会报错

  

【Map的初始化】使用 make 来分配内存

testMap := make(map[string]int, 5)

  

【Map的无序】

package main

import "fmt"

func main() {
	testMap := make(map[string]int, 5)
	testMap["第一"] = 1
	testMap["第二"] = 2
	testMap["第三"] = 3
	testMap["第四"] = 4
	testMap["第五"] = 5
	for k, v := range testMap {
		fmt.Printf("%v %v | ", k, v)
	}
}

多执行几次,每次打印的顺序都不一样

第四 4 | 第五 5 | 第一 1 | 第二 2 | 第三 3 | 
第三 3 | 第四 4 | 第五 5 | 第一 1 | 第二 2 | 
第四 4 | 第五 5 | 第一 1 | 第二 2 | 第三 3 | 
第一 1 | 第二 2 | 第三 3 | 第四 4 | 第五 5 | 

  

【Map的基本操作】

package main

import "fmt"

func main() {
	// 初始化map
	testMap := make(map[string]int, 5)

	// 为map添加键值对
	testMap["第一"] = 1
	testMap["第二"] = 2
	testMap["第三"] = 3
	testMap["第四"] = 4
	testMap["第五"] = 5

	// 判断key是否存在
	v, ok := testMap["第二"]
	if ok {
		fmt.Println("该键存在,值为", v)
	}

	// 遍历map
	for k, v := range testMap {
		fmt.Printf("%v %v | ", k, v)
	}
	
	// 删除键值对
	delete(testMap, "第一")
    
     // 获取map长度
     len(map)
       }

  

 

函数

 

【func的定义】函数是组织好的、可重复使用的、用于执行指定任务的代码块,Go语言中支持函数、匿名函数和闭包

【函数声明,函数的使用,函数多个返回值】

package main

import "fmt"

// 无返回值函数
func say() {
	fmt.Println("Hello 沙河")
}

// 单返回值函数
func add() int {
	return 1
}

// 单返回值函数
func double() (int, string) {
	return 1, "hehe"
}

func main() {
	// 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也称不能重名(包的概念详见后文)。
	// 参数  :参数由参数变量和参数变量的类型组成,多个参数之间使用,分隔。
	// 返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用()包裹,并用,分隔。
	// 函数体:实现指定功能的代码块。
	
	// func 函数名(参数)(返回值){
	// 	函数体
	// }

	say()
	a := add()
	b, c := double()
	fmt.Println(a, b, c)
}

  

【函数参数】

 

类型简写:多个同样类型的参数可以这么写

func intSum(x, y int) int {
	return x + y
}

  

可变参数:参数数量不固定,通常作为函数的最后一个参数。函数的可变参数是通过切片来实现的

func intSum2(x ...int) int {
	fmt.Println(x) //x是一个切片
	sum := 0
	for _, v := range x {
		sum = sum + v
	}
	return sum
}

调用

ret4 := intSum2(10, 20, 30)
fmt.Println(ret4)    // 60

当 固定参数+可变参数 时

func intSum3(x int, y ...int) int {
	fmt.Println(x, y)
	sum := x
	for _, v := range y {
		sum = sum + v
	}
	return sum
}

调用


ret8 := intSum3(100, 10, 20, 30)
fmt.Println(ret8)    // 160

  

参数为引用传递

package main

import "fmt"

// 参数为int类型的指针
func swap(x *int, y *int) {
	var temp int 
	temp = *x    // 将4赋值给了temp
	*x = *y      // 将6赋值给了x
	*y = temp    // 将4赋值给了y
 }

func main() {
	var x int = 4
	var y int = 6
	swap(&x, &y)
	fmt.Println(x, y)
}

  

【匿名函数】匿名函数就是没有函数名的函数

// 将匿名函数保存到变量
add := func(x, y int) {
	fmt.Println(x + y)
}
add(10, 20) // 通过变量调用匿名函数


//自执行函数:匿名函数定义完加()直接执行
func(x, y int) {
	fmt.Println(x + y)
}(10, 20)

  

【函数的闭包】

package main

import "fmt"

// 返回值是一个函数
func adder() func(int) int {
	var x int
	return func(y int) int {
		x += y
		return x
	}
}

func main() {
	var f = adder()	// f就是func(y int) int {}, f(10)就是 y = 10 作为参数代入,返回一个int
	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
}

进阶闭包函数

func adder2(x int) func(int) int {
	return func(y int) int {
		x += y
		return x
	}
}

func main() {
	var f = adder2(10)    // x一直活在f的声明周期内
	fmt.Println(f(10)) //20
	fmt.Println(f(20)) //40
	fmt.Println(f(30)) //70

	f1 := adder2(20)
	fmt.Println(f1(40)) //60
	fmt.Println(f1(50)) //110
}

  

【panic和recover】

package main

import "fmt"

func funcA() {
	fmt.Println("func A")
}

func funcB() {
	defer func() {
		err := recover()
		//如果程序出出现了panic错误,可以通过recover恢复过来
		if err != nil {
			fmt.Println("recover in B")
		}
	}()
	panic("panic in B")  // 此处模拟可能引发panic的语句
}

func funcC() {
	fmt.Println("func C")
}
func main() { funcA() funcB() funcC() }

结果

func A
recover in B
func C

 recover()必须搭配defer使用。defer一定要在可能引发panic的语句之前定义。

 

 

指针

【指针概念】任何程序数据载入内存后,在内存都有他们的地址,这就是指针

【声明指针】

var a *int    

  

【取指针地址】

ptr := &v    // v的类型为T

v:代表被取地址的变量,类型为T
ptr:用于接收地址的变量,ptr的类型就为*T,称做T的指针类型。*代表指针。

  

 

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:10

取地址操作符&和取值操作符*是一对互补操作符,&取出地址,*根据地址取出地址指向的值。

变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:

  对变量进行取地址(&)操作,可以获得这个变量的指针变量。
  指针变量的值是指针地址。
  对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。

 

【空指针】一个变量只是声明了,没有分配内存地址,这个变量的指针就是空的

package main

import "fmt"

func main() {
	var v map[string]int   // 声明了变量,但并未分配内存地址
	//v["hehe"] = 1
	fmt.Printf("%p", v)	// 0x0

	var b int
	fmt.Printf("%v",&b)	// 0xc0000120f0
}

  

【new和make】二者都是用来做内存分配的。

new不大常用,func new(Type) *Type

Type表示类型,new函数只接受一个参数,这个参数是一个类型
*Type表示类型指针,new函数返回一个指向该类型内存地址的指针。并且该指针对应的值为该类型的零值

func main() {
	a := new(int)
	b := new(bool)
	fmt.Printf("%T\n", a) // *int
	fmt.Printf("%T\n", b) // *bool
	fmt.Println(*a)       // 0
	fmt.Println(*b)       // false
}	

按照如下方式使用内置的new函数对a进行初始化之后就可以正常对其赋值了:

func main() {
	var a *int
	a = new(int)
	*a = 10
	fmt.Println(*a)
}

  

make只用于slice、map以及chan的内存创建,它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。

func main() {
	var b map[string]int
	b = make(map[string]int, 10)
	b["沙河娜扎"] = 100
	fmt.Println(b)
}

  

结构体

【结构体概念】Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct

【结构体定义】

type person struct {
	name string
	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}
}

或者

type person struct {
	name string
	age int
}

func main() {
	a := person {
		name: "heihei",
		age: 18,
	}

	fmt.Println(a)
}

  

【结构体内存布局】结构体占用一块连续的内存。

type test struct {
	a int8
	b int8
	c int8
	d int8
}

n := test{
	1, 2, 3, 4,
}

fmt.Printf("n.a %p\n", &n.a)
fmt.Printf("n.b %p\n", &n.b)
fmt.Printf("n.c %p\n", &n.c)
fmt.Printf("n.d %p\n", &n.d)


输出:
n.a 0xc0000a0060
n.b 0xc0000a0061
n.c 0xc0000a0062
n.d 0xc0000a0063

  

【空结构体】空结构体是不占用空间的。

var v struct{}
fmt.Println(unsafe.Sizeof(v))  // 0

  

【结构体继承和多态】

package main

import "fmt"

type Point struct {
    x int
    y int
}
 
func (p Point) show() {		// point的show方法
    fmt.Println("point.show")
}


type Circle struct {
    Point     // 匿名内嵌结构体
    Radius int
}

func (c Circle) show() {	// circle的show方法
	fmt.Println("Circle.show")
}

func main() {
    var c = Circle {
        Radius: 50,
    }
     
    fmt.Printf("%+v\n", c)
    fmt.Printf("%+v\n", c.Point)
    fmt.Printf("%d %d\n", c.x, c.y) // 继承了字段
    fmt.Printf("%d %d\n", c.Point.x, c.Point.y)
    c.show() // 这里是circle的show方法。如果没有定义cricle的show方法,那么这里就继承了point的show方法
c.Point.show()  // 这里是point的show方法,
}

结果:

{Point:{x:0 y:0} Radius:50}
{x:0 y:0}
0 0
0 0
Circle.show
point.show

  

先猜猜GO语言支不支持多态

package main

import "fmt"

type Point struct {
    x int
    y int
}
 
func (p Point) show() {		// point的show方法
	fmt.Println("point.show")
	p.test()	// 当Point被嵌套在Circle中,这个test方法依然调用的是Point的test方法
}

func (p Point) test() {		// point的test方法
	fmt.Println("this is Point test")
}

type Circle struct {
    Point     // 匿名内嵌结构体
    Radius int
}

func (c Circle) test() {	// circle的test方法
	fmt.Println("this is Circle test")
}

func main() {
    var c = Circle {
        Radius: 50,
    }
    c.show() 
}

如果GO语言支持多态,那么这个c.show()中调用的test方法应该是Circle中的test方法,因为子类重写了test方法,然而结果:

point.show
this is Point test

子类的test方法并没有被调用,很明显GO语言不支持多态,那么如何来通过其他办法模拟多态呢?

 

GO语言通过接口来模拟多态

package main

import "fmt"

type Fruitable interface {
    eat()  // 该接口有个eat方法,谁嵌套它,它就调用谁的eat()
}

type Fruit struct {
    Name string  // 属性变量
    Fruitable  // 这是一个接口
}

func (f Fruit) want() {
    fmt.Printf("I like ")
    f.eat() // 外结构体会自动继承匿名内嵌变量的方法
}

type Apple struct {}

func (a Apple) eat() {
    fmt.Println("eating apple")
}

type Banana struct {}

func (b Banana) eat() {
    fmt.Println("eating banana")
}

func main() {
    var f1 = Fruit{"Apple", Apple{}}
    var f2 = Fruit{"Banana", Banana{}}
    f1.want()
    f2.want()
}

---------
I like eating apple
I like eating banana

如果此时Fruit有自己的eat()方法,那么就失去模拟多态的效果了,调用的依然是Fruit的eat方法

posted on 2020-10-14 16:45  longzhankunlun  阅读(121)  评论(0编辑  收藏  举报