Go语言学习笔记

Go语言学习笔记

学习自菜鸟教程

编译运行Go程序

// 要执行 Go 语言代码可以使用 go run 命令。
$ go run hello.go 
Hello, World!
// 此外我们还可以使用 go build 命令来生成二进制文件:
$ go build hello.go 
$ ls
hello    hello.go
$ ./hello 
Hello, World!

Go 语言结构

Go 语言的基础组成有以下几个部分:

  • 声明
  • 引入包
  • 函数
  • 变量
  • 语句 & 表达式
  • 注释

Go 程序结构组成关系 应用程序->文件夹->文件->包->函数

Go应用程序包含多个包,其中必有一个名为main的包,也只有此包可以包含 main 函数

main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有init函数则会先执行该函数)

​ 文件夹包含文件,文件夹和文件和包名没有任何关系,同意文件夹内的所有文件只能有一个包名

包包含多个文件,文件通过package 包名,说明隶属于哪个包,通过import 包名,说明引用哪个包

Go 语言基础语法

Go标记

Go程序由标记组成,或者说程序由字母组成,标记可以是关键字,标识符,常量,字符串,符号

功能角度讲,程序一般由关键字、常量、变量、运算符、类型、分隔符、标点符号和函数组成。

行分隔符

​ 在 Go 程序中,一行代表一个语句结束

注释

​ 同C++/C

标识符

​ 标识符用来命名变量、类型等程序实体

字符串连接

​ +

关键字

​ 关键字,保留字,预定义标识符

Go语言数据类型

在 Go 编程语言中,数据类型用于声明函数和变量。

数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存。

几种数据类型

​ 布尔型,数字类型,字符串类型,派生类型

​ 其中派生类型包含,

(a) 指针类型(Pointer)

(b) 数组类型

© 结构化类型(struct)

(d) Channel 类型

(e) 函数类型

(f) 切片类型

(g) 接口类型(interface)

(h) Map 类型

Go语言变量

变量来源于数学,是计算机语言中能储存计算结果或能表示值抽象概念。

变量可以通过变量名访问

三种变量声明

// 1. 指定变量类型,如果没有初始化,则变量默认为零值
var v_name v_type
v_name = value
/*
零值就是变量没有做初始化时系统默认设置的值
数值类型(包括complex64/128)为 0
布尔类型为 false
字符串为 ""(空字符串)
以下几种类型为 nil:
*/
var a *int
var a []int
var a map[string] int
var a chan int
var a func(string) int
var a error // error 是接口

// 2. 根据值自行判定变量类型
var v_name = value

// 3. 省略 var, 注意 := 左侧如果没有声明新的变量,就产生编译错误
v_name := value

a := 50是使用变量的首选形式,但是它只能被用在函数体内,而不可以用于全局变量的声明与赋值。使用操作符 := 可以高效地创建一个新的变量,称之为初始化声明

多变量声明

//类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3

var vname1, vname2, vname3 = v1, v2, v3 // 和 python 很像,不需要显示声明类型,自动推断

vname1, vname2, vname3 := v1, v2, v3 // 出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误


// 这种因式分解关键字的写法一般用于声明全局变量
var (
    vname1 v_type1
    vname2 v_type2
)

并行赋值

如果你想要交换两个变量的值,则可以简单地使用 a, b = b, a,两个变量的类型必须是相同。

空白标识符 _ 也被用于抛弃值,如值 5 在:_, b = 5, 7 中被抛弃。

_ 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。

并行赋值也被用于当一个函数返回多个返回值时,比如这里的 val 和错误 err 是通过调用 Func1 函数同时得到:val, err = Func1(var1)。

变量注意事项

  1. 如果在相同的代码块中,我们不可以再次对于相同名称的变量使用初始化声明,例如:a := 20 就是不被允许的,编译器会提示错误 no new variables on left side of :=,但是 a = 20 是可以的,因为这是给相同的变量赋予一个新的值。

  2. 如果你在定义变量 a 之前使用它,则会得到编译错误 undefined: a。

  3. 如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误。但是全局变量是允许声明但不使用。

Go语言常量

常量是一个简单值的标识符,在程序运行时,不会被修改的量。

常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型

常量的定义

显示类型定义、隐式类型定义、多个同类型定义

const identifier [type] = value
const identifier = value
const c_name1, c_name2 = value1, value2

常量可以用len(), cap(),
unsafe.Sizeof()函数计算表达式的值。常量表达式中,函数必须是内置函数,否则编译不过:

package main

import "unsafe"
const (
    a = "abc"
    b = len(a)
    c = unsafe.Sizeof(a)
)

func main(){
    println(a, b, c)
}
abc 3 16

字符串类型在 go 里是个结构, 包含指向底层数组的指针和长度,这两部分每部分都是 8 个字节,所以字符串类型大小为 16 个字节。

iota 特殊常量

​ iota 只是在同一个 const 常量组内逐行递增,不管是否显式使用,每当有新的 const 关键字时,iota 计数会重新开始

​ 可以认为是一个可以被编译器修改的常量

package main
import "fmt"

func main() {
    const (
            a = iota   //0
            b          //1
            c          //2
            d = "ha"   //独立值,iota += 1
            e          //"ha"   iota += 1
            f = 100    //iota +=1
            g          //100  iota +=1
            h = iota   //7,恢复计数
            i          //8
    )
    fmt.Println(a,b,c,d,e,f,g,h,i)
}

常量组的定义

​ 在定义常量组时,如果不提供初始值,则表示将使用上行的表达式。

​ iota 只是在同一个 const 常量组内递增,每当有新的 const 关键字时,iota 计数会重新开始

Go 语言运算符

运算符用于在程序运行时执行数学或逻辑运算。

Go 语言内置的运算符有:

算术运算符

关系运算符

逻辑运算符

位运算符

赋值运算符

其他运算符 &*

运算符优先级

有些运算符拥有较高的优先级,二元运算符的运算方向均是从左至右。下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低:

优先级运算符 * / % << >> & &^ + - | ^ == != < <= > >= && ||
5* / % << >> & &^
4+ -
3== != < <= > >=
2&&
1||

怎么记,赋值运算符基本属于最底层,只大于&&和||,之上加减|^是一类,再往上*/&等是一类

​ 指针变量 * 和地址值 & 的区别:指针变量保存的是一个地址值,会分配独立的内存来存储一个整型数字。当变量前面有 * 标识时,才等同于 & 的用法,否则会直接输出一个整型数字

func main() {
   var a int = 4
   var ptr *int
   ptr = &a
   println("a的值为", a);    // 4
   println("*ptr为", *ptr);  // 4
   println("ptr为", ptr);    // 824633794744
}

Go 的自增,自减只能作为表达式使用,而不能用于赋值语句。

a++ // 这是允许的,类似 a = a + 1,结果与 a++ 相同
a-- //与 a++ 相似
a = a++ // 这是不允许的,会出现变异错误 syntax error: unexpected ++ at end of statement

Go 语言条件语句

Go 没有三目运算符,所以不支持 ?: 形式的条件判断

五种条件判断语句

if 布尔表达式 {
   /* 在布尔表达式为 true 时执行 */
}

if 布尔表达式 {
   /* 在布尔表达式为 true 时执行 */
} else {
  /* 在布尔表达式为 false 时执行 */
}

if 布尔表达式 1 {
   /* 在布尔表达式 1 为 true 时执行 */
   if 布尔表达式 2 {
      /* 在布尔表达式 2 为 true 时执行 */
   }
}

switch

\1. switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上至下逐一测试,直到匹配为止

\2. switch 默认情况下 case 最后自带 break 语句,匹配成功后就不会执行其他 case,如果我们需要执行后面的 case,可以使用 fallthrough

变量 var1 可以是任何类型,而 val1 和 val2 则可以是同类型的任意值。类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式。

switch var1 {
    case val1:
        ...
    case val2:
        ...
    case val3, val4, val5:
    	...
    default:
        ...
}

type-switch

​ switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型。

switch x.(type){
    case type:
       statement(s);      
    case type:
       statement(s); 
    /* 你可以定义任意个数的case */
    default: /* 可选 */
       statement(s);
}

package main
import "fmt"
func main() {
   var x interface{}
     
   switch i := x.(type) {
      case nil:  
         fmt.Printf(" x 的类型 :%T",i)                
      case int:  
         fmt.Printf("x 是 int 型")                      
      case float64:
         fmt.Printf("x 是 float64 型")          
      case func(int) float64:
         fmt.Printf("x 是 func(int) 型")                      
      case bool, string:
         fmt.Printf("x 是 bool 或 string 型" )      
      default:
         fmt.Printf("未知型")    
   }  
}

fallthrough

使用 fallthrough 会强制执行后面的一条 case 语句,fallthrough 不会判断下一条 case 的表达式结果是否为 true。

break和fallthrough技巧


switch{
    case 1:
   	 ...
   	 if(...){
   	     break
  	 }
    	fallthrough // 此时switch(1)会执行case1和case2,但是如果满足if条件,则只执行case1
    case 2:
    	...
    case 3:
}

Go 语言循环语句

两种循环类型

for
// 和 C 语言的 for 一样:
for init; condition; post { }

// 和 C 的 while 一样:
for condition { }

// 和 C 的 for(;;) 一样:
for { }

// for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。格式如下:
for key, value := range oldMap {
    newMap[key] = value
}

循环嵌套
for [condition |  ( init; condition; increment ) | Range]
{
   for [condition |  ( init; condition; increment ) | Range]
   {
      statement(s);
   }
   statement(s);
}

三种循环控制语句

break

continue

​ Go 语言的 continue 语句 有点像 break 语句。但是 continue 不是跳出循环,而是跳过当前循环执行下一次循环语句。或者说执行 continue 语句会触发for增量语句的执行。

goto

Go 语言的 goto 语句可以无条件地转移到过程中指定的行。

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 10

   /* 循环 */
   LOOP: for a < 20 {
      if a == 15 {
         /* 跳过迭代 */
         a = a + 1
         goto LOOP
      }
      fmt.Printf("a的值为 : %d\n", a)
      a++    
   }  
}

Go 语言函数

函数定义


func function_name( [parameter list] ) [return_types] {
   函数体
}

func max(num1, num2 int) int {
   /* 声明局部变量 */
   var result int

   if (num1 > num2) {
      result = num1
   } else {
      result = num2
   }
   return result
}

func swap(x, y string) (string, string) {
   return y, x
}

函数的创建和调用

func max(num1, num2 int) int {
   /* 定义局部变量 */
   var result int
   if (num1 > num2) {
      result = num1
   } else {
      result = num2
   }
   return result
}

func main(){
   /* 1. 直接使用函数实体 */
   /* 每次使用max代表创建新的函数实体 */
   fmt.Println(max(1,2))

   /* 2. MaxOne 是一个函数,而不是max()函数的返回值result */
   /* 每次使用MaxOne代表的都是同一个函数实体,在下面的闭包代码可以看到区别*/
   /* 下面哪种正确,问题来自,下面的 闭包携带参数 */
   MaxOne := max()
   fmt.Println(MaxOne(1,2))

   MaxOne := max(1,2)
   fmt.Println(MaxOne())

   /* 3. 声明函数变量 */
   getSquareRoot := func(x float64) float64 {
      return math.Sqrt(x)
   }
   /* 使用函数 */
   fmt.Println(getSquareRoot(9))
}

函数参数

​ 函数如果使用参数,该变量可称为函数的形参。

形参就像定义在函数体内的局部变量。

调用函数,可以通过两种方式来传递参数

\1. 值传递:值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

\2. 引用传递:引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

两者的区别就在于,1,函数调用时,传递的是变量的地址 同时2. 函数参数列表使用的是指针类型

函数用法

\1. 函数作为另外一个函数的实参

Go 语言可以很灵活的创建函数,并作为另外一个函数的实参。

func main(){
   /* 声明函数变量 */
   getSquareRoot := func(x float64) float64 {
      return math.Sqrt(x)
   }

   /* 使用函数 */
   fmt.Println(getSquareRoot(9))
}

\1. 闭包

Go 语言支持匿名函数,可作为闭包。匿名函数是一个"内联"语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必声明。

以下实例中,我们创建了函数 getSequence() ,返回另外一个函数。该函数的目的是在闭包中递增 i 变量

还是看代码吧,实践中理解这样写的作用

package main
import "fmt"

func getSequence() func() int {
   i:=0
   return func() int {
      i+=1
     return i  
   }
}

func main(){
   /* nextNumber 为一个函数,函数 i 为 0 */
   nextNumber := getSequence()  

   /* 调用 nextNumber 函数,i 变量自增 1 并返回 */
   fmt.Println(nextNumber()) // 1
   fmt.Println(nextNumber()) // 2
   fmt.Println(nextNumber()) // 3
   
   /* 创建新的函数 nextNumber1,并查看结果 */
   nextNumber1 := getSequence()  
   fmt.Println(nextNumber1()) // 1
   fmt.Println(nextNumber1()) // 2
}

闭包携带参数
package main

import "fmt"
func main() {
    add_func := add(1,2)
    fmt.Println(add_func())
    fmt.Println(add_func())
    fmt.Println(add_func())
}

// 闭包使用方法
func add(x1, x2 int) func()(int,int)  {
    i := 0
    return func() (int,int){
        i++
        return i,x1+x2
    }
}

func main() {
    add_func := add(1,2)
    fmt.Println(add_func(1,1))
    fmt.Println(add_func(0,0))
    fmt.Println(add_func(2,2))
} 
// 闭包使用方法
func add(x1, x2 int) func(x3 int,x4 int)(int,int,int)  {
    i := 0
    return func(x3 int,x4 int) (int,int,int){ 
       i++
       return i,x1+x2,x3+x4
    }
}

\1. 方法

Go 语言中同时有函数和方法

一个方法就是一个包含了接受者的函数

接受者可以是自定义类型(命名类型或者结构体类型)的一个值或者是一个指针。

所有给定类型的方法属于该类型的方法集。就像C++中类的方法一样

func (variable_name variable_data_type) function_name() [return_type]{
   /* 函数体*/
}

package main
import (
   "fmt"  
)

/* 定义结构体 */
type Circle struct {
  radius float64
}

func main() {
  var c1 Circle
  c1.radius = 10.00
  fmt.Println("圆的面积 = ", c1.getArea())
}

//该 method 属于 Circle 类型对象中的方法
func (c Circle) getArea() float64 {
  //c.radius 即为 Circle 类型对象中的属性
  return 3.14 * c.radius * c.radius
}

C++与Go在方法上的区别和联系

C++ 等语言中,实现类的方法做法都是编译器隐式的给函数加一个 this 指针,而在 Go 里,这个 this 指针需要明确的申明出来,其实和其它 OO 语言并没有很大的区别。

在 C++ 中是这样的:

// c++
class Circle {
  public:
    float getArea() {
       return 3.14 * radius * radius;
    }
  private:
    float radius;
}

// 其中 getArea 经过编译器处理大致变为
float getArea(Circle *const c) {
  ...
}

// go 
func (c Circle) getArea() float64 {
  //c.radius 即为 Circle 类型对象中的属性
  return 3.14 * c.radius * c.radius
}

Go 语言变量作用域

Go 语言中变量可以在三个地方声明:

· 函数内定义的变量称为局部变量

· 函数外定义的变量称为全局变量

· 函数定义中的变量称为形式参数

局部变量

​ 在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,(形式参数)参数和返回值变量也是局部变量。

全局变量

package main
import "fmt"

/* 声明全局变量 */
var g int
func main() {

}

在函数体外声明的变量称之为全局变量,全局变量可以在整个包甚至外部包(被导出后)使用

​ Go 语言程序中全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑

Go 语言数组

声明数组

var variable_name [SIZE] variable_type

初始化数组

var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
// 初始化数组中 {} 中的元素个数不能大于 [] 中的数字

var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

var balance = [5] int {1,2} // 1,2,0,0,0
// 在初始化时没有指定初值的元素将会赋值为其元素类型 int 的默认值0,string 的默认值是 ""。

var balance = [...] int {2:1,4:3} // 0,0,1,0,3

访问数组元素

数组更多用法

\1. 多维数组
var variable_name [SIZE][SIZE2]...[SIZEN] variable_type

初始化二维数组

a = [3][4]int{  
 {0, 1, 2, 3} ,   /*  第一行索引为 0 */
 {4, 5, 6, 7} ,   /*  第二行索引为 1 */
 {8, 9, 10, 11},  /* 第三行索引为 2 */
}
// 以上代码中倒数第二行的 } 必须要有逗号,因为最后一行的 } 不能单独一行,也可以写成这样:
a = [3][4]int{  
 {0, 1, 2, 3} ,   /*  第一行索引为 0 */
 {4, 5, 6, 7} ,   /*  第二行索引为 1 */
 {8, 9, 10, 11}}   /* 第三行索引为 2 */

\1. 向函数传递数组

如果你想向函数传递数组参数,你需要在函数定义时,声明形参为数组,我们可以通过以下两种方式来声明

void myFunction(param [10]int)
{...}

void myFunction(param []int)
{...}

Go 语言指针

指针

变量是一种使用方便的占位符,用于引用计算机内存地址

​ 一个指针变量指向了一个值的内存地址。类似于变量和常量,在使用指针前你需要声明指针。

​ 总体来说指针是一种变量,这种变量的值是地址值,当然不是无意义的地址值,而是某种类型某种值的地址值,同样的指针可以赋值,和一般变量赋值没什么不同,但是赋的值是变量的地址值&var。比其他变量多的一点是可以可以访问指针指向的值(那块地址的值),有个*运算,同时可以改变那块地址存储的值,*var = 20

var var_name *var-type
使用指针

· 定义指针变量。

· 为指针变量赋值。

· 访问指针变量中指向地址的值

package main

import "fmt"

func main() {
   var a int= 20   /* 声明实际变量 */
   var ip *int        /* 声明指针变量 */

   ip = &a  /* 指针变量的存储地址 */

   fmt.Printf("a 变量的地址是: %x\n", &a  )

   /* 指针变量的存储地址 */
   fmt.Printf("ip 变量储存的指针地址: %x\n", ip )

   /* 使用指针访问值 */
   fmt.Printf("*ip 变量的值: %d\n", *ip )
}

空指针

​ 当一个指针被定义后没有分配到任何变量时,它的值为 nil。nil 指针也称为空指针。nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。

一个指针变量通常缩写为 ptr

package main
import "fmt"

func main() {
   var  ptr *int
   fmt.Printf("ptr 的值为 : %x\n", ptr  ) // 0
}

if(ptr != nil) /* ptr 不是空指针 */

指针更多用法

\1. Go 语言指针数组

var ptr [MAX]*int;

\1. Go 语言指向指针的指针

如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。

当定义一个指向指针的指针变量时,第一个指针存放第二个指针的地址,第二个指针存放变量的地址

var ptr **int;

\1. Go 语言指针作为函数参数

Go 语言允许向函数传递指针,只需要在函数定义的参数上设置为指针类型即可

Go 语言结构体

Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。

结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。

定义结构体

type struct_variable_type struct {
   member definition;
   member definition;
   ...
   member definition;
}

variable_name := structure_variable_type {value1, value2...valuen}
或
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}

package main
import "fmt"
type Books struct {
   title string
   author string
   subject string
   book_id int
}

func main() {
    // 创建一个新的结构体
    fmt.Println(Books{"Go 语言", "www.runoob.com", "Go 语言教程", 6495407})
    // 也可以使用 key => value 格式
    fmt.Println(Books{title: "Go 语言", author: "www.runoob.com", subject: "Go 语言教程", book_id: 6495407})
    // 忽略的字段为 0 或 空
    fmt.Println(Books{title: "Go 语言", author: "www.runoob.com"})
}

访问结构体成员

\1. 通过 struct 变量.成员 变量来访问。

\2. 通过 struct 指针.成员 变量来访问。

结构体作为函数参数

​ 同其他类型变量一样

结构体指针

​ 注意和C++结构体指针访问成员变量的区别

Go 语言切片(Slice)

Go 语言切片是对数组的抽象。

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大

切片,实际的是获取数组的某一部分,len切片<=cap切片<=len数组,切片由三部分组成:指向底层数组的指针、len、cap

定义切片/声明切片

var identifier []type
var slice1 []type = make([]T, length, capacity) // 这里 len 是数组的长度并且也是切片的初始长度,也可以指定容量,其中capacity为可选参数
// 注意定义切片的时候,长度不能写在[]里,[]代表的是切片类型,而数组[]里可以写数字or…表示数组长度

创建切片/初始化切片

s := [] int {1,2,3} 
// 直接初始化切片,[]表示是切片类型,{1,2,3}初始化值依次是1,2,3.其cap=len=3
s := [3] int {1,2,3} 
s := [] int {1,2,3} 
// 以上两者都是数组的初始化,和切片分清
s := arr[:] 
// 初始化切片s,是数组arr的引用
s := arr[startIndex:endIndex] 
// 将arr中从下标startIndex到endIndex-1 下的元素创建为一个新的切片
s := arr[startIndex:] 
// 默认 endIndex 时将表示一直到arr的最后一个元素
s := arr[:endIndex] 
// 默认 startIndex 时将表示从arr的第一个元素开始
s1 := s[startIndex:endIndex] 
// 通过切片s初始化切片s1
s := make([]int, len, cap) 
// 通过内置函数make()初始化切片s,[]int 标识为其元素类型为int的切片

len和cap函数

​ 切片是可索引的,并且可以由 len() 方法获取长度。

切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。

package main
import "fmt"

func main() {
   var numbers = make([]int,3,5)
   printSlice(numbers)
}
func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x) 
   // len=3 cap=5 slice=[0 0 0]
}

空切片

​ 一个切片在未初始化之前默认为 nil,长度为 0

var numbers []int
// len=0 cap=0 slice=[]
	

切片截取

​ 可以通过设置下限及上限来设置截取切片 [lower-bound:upper-bound]

​ 表示索引lower-bound/0 到 upper-bound – 1/max-1

append和 copy函数

​ 增加元素append,前提是容量范围内

​ 如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝

package main
import "fmt"

func main() {
   var numbers []int
   printSlice(numbers)

   /* 允许追加空切片 */
   numbers = append(numbers, 0)
   printSlice(numbers)

   /* 向切片添加一个元素 */
   numbers = append(numbers, 1)
   printSlice(numbers)

   /* 同时添加多个元素 */
   numbers = append(numbers, 2,3,4)
   printSlice(numbers)

   /* 创建切片 numbers1 是之前切片的两倍容量*/
   numbers1 := make([]int, len(numbers), (cap(numbers))*2)

   /* 拷贝 numbers 的内容到 numbers1 */
   copy(numbers1,numbers)
   printSlice(numbers1)  
}

func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
/*
len=0 cap=0 slice=[]
len=1 cap=1 slice=[0]
len=2 cap=2 slice=[0 1]
len=5 cap=6 slice=[0 1 2 3 4]
len=5 cap=12 slice=[0 1 2 3 4]
 */

切片其他问题

\1. 我们基于原数组或者切片创建一个新的切片后,那么新的切片的大小和容量是多少呢?

这里有个公式,对于底层数组容量是 k 的切片 slice[i:j] 来说:长度: j-i,容量: k-i

\2. 切片是引用传递,数组是值传递,在函数调用时

\3. 使用 copy 函数要注意对于 copy(dst, src),要初始化 dst 的 size,否则无法复制

\4. 切片内部结构:

第一个字段表示 array 的指针,是真实数据的指针第二个是表示 slice 的长度,第三个是表示 slice 的容量。

所以 unsafe.Sizeof(切片)永远都是 24。

当把 slice 作为参数,本身传递的是值,但其内容就 byte* array,实际传递的是引用,所以可以在函数内部修改,但如果对 slice 本身做 append,而且导致 slice 进行了扩容,实际扩容的是函数内复制的一份切片,对于函数外面的切片没有变化。

struct Slice
{   
    byte*    array;       // actual data
    uintgo    len;        // number of elements
    uintgo    cap;        // allocated number of elements
};

func slice_value(slice_test []int) {
    slice_test[1] = 100                // 函数外的slice确实有被修改
    slice_test = append(slice_test, 6) // 函数外的不变
    fmt.Printf("slice_value:%#v,%#v,%#v\n", slice_test, len(slice_test), cap(slice_test))
}

func slice_ptr(slice_test *[]int) { // 这样才能修改函数外的slice
    *slice_test = append(*slice_test, 7)
    fmt.Printf("slice_ptr:%#v,%#v,%#v\n", *slice_test, len(*slice_test), cap(*slice_test))
}
5.	slice 的底层是数组指针,所以 slice a 和 s 指向的是同一个底层数组,所以当修改 s[0] 时,a 也会被修改
package main
import "fmt"
func main() {
    s := []int{1, 2, 3} // len=3, cap=3
    a := s
    s[0] = 888
    s = append(s, 4)
    fmt.Println(a, len(a), cap(a)) // 输出:[888 2 3] 3 3
    fmt.Println(s, len(s), cap(s)) // 输出:[888 2 3 4] 4 6
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值