golang入门

1.入门简介

Go语言出现的背景

最近十年来,C/C在计算领域没有很好得到发展,并没有新的系统编程语言出现。对开发程度和系统效率在很多情况下不能兼得。要么执行效率高,但低效的开发和编译,如C;要么执行低效,但拥有有效的编译,如.NET、Java;所以需要一种拥有较高效的执行速度、编译速度和开发速度的编程语言,Go就横空出世了。

# 传统的语言比如c++,大家花费太多时间来学习如何使用这门语言,而不是如何
更好的表达写作者的思想,同时编译花费的时间实在太长,对于编写-编译-运行这
个链条来说周期太长。动态语言如Python,由于没有强类型的约束,很多问题需
要在运行时发现,这种低级错误更应该交给编译器来发现。
  • 人力成本越来越高
  • 机器越来越便宜
  • 机器的性能越来越厉害
  • 在开发效率和运行速度上达到平衡

go出现之前,无论汇编语言、还是动态脚本语言,在执行效率和开发效率上都不能兼备。

执行效率 execution speed: C/C++ > Java > PHP
开发效率 developing efficiency: PHP > Java > C/C++

Go之所以叫Go,是想表达这门语言的运行速度、开发速度、学习速度(develop)都像gopher一样快。

gopher是一种生活在加拿大的小动物,go的吉祥物就是这个小动物, 它的中文名叫做囊地鼠,他们最大的特点就是挖洞速度特别快,当然可能不止是挖洞啦。

Go语言的特色:

  • 没有继承多态的面向对象
  • 强一致类型
  • interface不需要显式声明(Duck Typing)
  • 没有异常处理(Error is value)
  • 基于首字母的可访问特性
  • 不用的import或者变量引起编译错误
  • 完整而卓越的标准库包
  • Go内置runtime(作用是性能监控、垃圾回收等)

2.基础语法

1.变量

变量是为存储特定类型的值而提供给内存位置的名称。在go中声明变量有多种语法。所以变量的本质就是一小块内存,用于存储数据,在程序运行过程中数值可以改变。

package main

import "fmt"

func main() {
   /*
   变量:variable
   概念:一小块内存,用于存储数据,在程序的运行过程中数值可以改变
      使用:step1 变量的声明,也叫定义
          step2 变量的访问,赋值和取值
    */

   //变量的使用

   //第一种:定义变量,然后赋值

   var age int
   age = 30
   if age > 18 {
      //Printf为格式化输出,%d为数字类型的占位符
      fmt.Printf("你的年纪已成年,当前年龄是%d\n",age)
   }
   //还可以写在同一行
   var num1 int = 19
   fmt.Printf("num1的数值是%d\n",num1)

   /*
    静态语言:强类型语言
         Go Java c++
    动态语言:弱类型语言
         JavaScript php python
    */

   //第二种,直接赋值
   var name = "葛威"
   fmt.Printf("我的名字是%s,名字类型为%T\n",name,name)

   //第三种,简短声明
   sum := 100
   fmt.Println(sum)

   //多个变量同时定义
   var a, b, c int
   a = 1
   b = 2
   c = 3
   //输出的每个对象之间会有空格
   fmt.Println(a,b,c)

   var num2,num3 int = 100, 200
   fmt.Println(num2, num3)

   var n1,f1,s1 = 100,3.14,"hello"
   fmt.Println(n1,f1,s1)

   var(
      studentName = "李小龙"
      age2 = 18
      sex = "女"
   )
   fmt.Printf("学生姓名:%s,年龄:%d,性别%s",studentName,age2,sex)
}

修改变量

func main() {
   var num int
   num = 10
    //%p为取地址的占位符
   fmt.Printf("num的数值是%d,地址为%p\n",num,&num)

   num = 20
   fmt.Printf("num的数值是%d,地址为%p",num,&num)
}

输出

num的数值是10,地址为0xc00000a0b8
num的数值是20,地址为0xc00000a0b8

注意事项

  • 变量必须先定义才能使用
  • go语言是静态语言,要求变量的类型和赋值的类型必须一致。
  • 变量名不能冲突。(同一个作用于域内不能冲突)
  • 简短定义方式,左边的变量名至少有一个是新的
  • 简短定义方式,不能定义全局变量。
  • 变量的零值。也叫默认值。
  • 变量定义了就要使用,否则无法通过编译。
2.常量

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

const identifier [type] = value
显式类型定义: const b string = "abc"
隐式类型定义: const b = "abc"
# 注意:定义了常量即使不使用也不会报错
const(
   a = 100
   b
)
fmt.Printf("a的数值是%d\n",a)
fmt.Printf("b的数值是%d\n",b)
a的数值是100
b的数值是100

	//如果定义一组常量,如果某个常量没有初始值,那么默认和上一行一致
枚举类型

Go中常用常量作为枚举类型

const(
	Spring = 0
	Summer = 1
	Autumn = 2
	winter = 3
)
iota

iota,特殊常量,可以认为是一个可以被编译器修改的常量,在一个常量组中,每定义一个常量iota就会加一,知道遇到一个新的const,此时这个iota就会置0.

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
    )
    const(
    	j = iota // 0
    )
    fmt.Println(a,b,c,d,e,f,g,h,i,j)
}
3.数据类型
基本数据类型

以下是go中可用的基本数据类型

布尔型bool

布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true

整数型

  • int8
    有符号 8 位整型 (-128 到 127)
    长度:8bit

  • int16
    有符号 16 位整型 (-32768 到 32767)

  • int32
    有符号 32 位整型 (-2147483648 到 2147483647)

  • int64
    有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)

  • uint8
    无符号 8 位整型 (0 到 255)
    8位都用于表示数值:

  • uint16
    无符号 16 位整型 (0 到 65535)

  • uint32
    无符号 32 位整型 (0 到 4294967295)

  • uint64
    无符号 64 位整型 (0 到 18446744073709551615)

    int和uint:根据底层平台,表示32或64位整数。除非需要使用特定大小的整数,否则通常应该使用int来表示整数。
    大小:32位系统32位,64位系统64位。
    范围:-2147483648到2147483647的32位系统和-9223372036854775808到9223372036854775807的64位系统。

浮点型

  • float32

    IEEE-754 32位浮点型数

  • float64

    IEEE-754 64位浮点型数

  • complex64

    32 位实数和虚数

  • complex128

    64 位实数和虚数

var f1 float32
var f2 float64
f1 = 3.14
f2 = 6.27
//%f为浮点数的占位符,%2.f为取小数点后2位
fmt.Printf("%T,%.3f\n",f1,f1)
fmt.Printf("%T,%.2f\n",f2,f2)

其他

  • byte

    类似 uint8

  • rune

    类似 int32

  • uint

    32 或 64 位

  • int

    与 uint 一样大小

  • uintptr

    无符号整型,用于存放一个指针

# byte = uint8
# rune = int32
# int = uint 
//取决于电脑操作系统 32位操作系统就是uint32 64位的即为 uint64

字符串型

字符串就是一串固定长度的字符连接起来的字符序列(多个byte的集合)。Go的字符串是由单个字节连接起来的。Go语言的字符串的字节使用UTF-8编码标识Unicode文本

//'A'和"A"的区别

v1 := 'A'
v2 := "A"
//首先输出下'A'的类型
fmt.Printf("%T",A);
//输出结果为int32 
fmt.Printf("%T,%d",v1,v1);
//输出结果为 int32 65 这就对应了ASCII码(美国信息交换标准代码)
	拓展  中国的编码表为 gbk,兼容了ASCII码,
	为了对其进行统一,一般采用国际的编码表:Unicode
	Unicode虽然制定了怎么编码,但是没有规定怎么对其进行存储,数值对应到2进制上又有了不同的表示方案:UTF-8 UTF-16 UTF-32 
v3 := '中'
fmt.Printf("%T,%d,%c,%q\n",v3,v3,v3,v3)

输出:int32,20013,中,'中'
转义字符
# 1 有些字符,有特殊的作用,可以将其转换为普通的字符 如 '\'字符
# 2 有些字符,就是普通的字符,转义后有特殊的字符 如 \t 制表符 \n 换行符
数据类型转换:Type Convert

语法格式:Type(Value)

常数:在有需要的时候,会自动转型

变量:需要手动转型 T(V)

注意点:兼容类型可以转换

var v1 = int8
var v2 = int16
v1 = 3
v1 = (int16)3
# 如果浮点类型转换为整型,那么取得是浮点类型的整数部分
# Go语言是静态语言,其定义、赋值、运算必须一致
4.运算符

算数运算符

+ - * / %(求余) ++ --在使用PrintfS输出时,使用%%代替%输出

关系运算符

== != > < >= <=

逻辑运算符

&&所谓逻辑与运算符。如果两个操作数都非零,则条件变为真
||所谓的逻辑或操作。如果任何两个操作数是非零,则条件变为真
!所谓逻辑非运算符。使用反转操作数的逻辑状态。如果条件为真,那么逻辑非操后结果为假

位运算符

ABA&BA|BA^B
00000
01011
11110
10011

赋值运算符

=简单的赋值操作符,分配值从右边的操作数左侧的操作数C = A + B 将分配A + B的值到C
+=相加并赋值运算符,它增加了右操作数左操作数和分配结果左操作数C += A 相当于 C = C + A
-=减和赋值运算符,它减去右操作数从左侧的操作数和分配结果左操作数C -= A 相当于 C = C - A
*=乘法和赋值运算符,它乘以右边的操作数与左操作数和分配结果左操作数C = A 相当于 C = C A
/=除法赋值运算符,它把左操作数与右操作数和分配结果左操作数C /= A 相当于 C = C / A
%=模量和赋值运算符,它需要使用两个操作数的模量和分配结果左操作数C %= A 相当于 C = C % A
<<=左移位并赋值运算符C <<= 2 相同于 C = C << 2
>>=向右移位并赋值运算符C >>= 2 相同于 C = C >> 2
&=按位与赋值运算符C &= 2 相同于 C = C & 2
^=按位异或并赋值运算符C = 2 相同于 C = C 2
|=按位或并赋值运算符C |= 2 相同于 C = C | 2

运算符优先级

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

优先级运算符
7~ ! ++ --
6* / % << >> & &^
5+ - ^
4== != < <= >= >
3<-
2&&
1||
5.键盘输入和打印输出
6.常用打印函数

打印:

func Print(a …interface{}) (n int, err error)

格式化打印:

func Printf(format string, a …interface{}) (n int, err error)

打印后换行

func Println(a …interface{}) (n int, err error)

格式化打印占位符:
            %v,原样输出
            %T,打印类型
            %t,bool类型
            %s,字符串
            %f,浮点
            %d,10进制的整数
            %b,2进制的整数
            %o,8进制
            %x,%X,16进制
                %x:0-9,a-f
                %X:0-9,A-F
            %c,打印字符
            %p,打印地址
            。。。
var x int
var y float64
fmt.Println("请输入一个整数,一个浮点类型:")
fmt.Scanln(&x,&y)//读取键盘的输入,通过操作地址,赋值给x和y   阻塞式
fmt.Printf("x的数值:%d,y的数值:%f\n",x,y)
//格式化输入的时候,要按照指定格式输入
fmt.Scanf("%d,%f",&x,&y)
fmt.Printf("x:%d,y:%f\n",x,y)

3.流程语句

1.if分支语句
if 布尔表达式 {
   /* 在布尔表达式为 true 时执行 */
}
if 布尔表达式 {
   /* 在布尔表达式为 true 时执行 */
} else {
  /* 在布尔表达式为 false 时执行 */
}
if 布尔表达式1 {
   /* 在布尔表达式1为 true 时执行 */
} else if 布尔表达式2{
   /* 在布尔表达式1为 false ,布尔表达式2为true时执行 */
} else{
   /* 在上面两个布尔表达式都为false时,执行*/
}
# if后的'{'一定要和if在同一行
# else一定跟在if的'}'之后,不可以另起一行

如果其中包含一个可选的语句组件(在评估条件之前执行),则还有一个变体。它的语法是

if statement; condition {  
}

if condition{

}
package main

import (  
    "fmt"
)

func main() {  
    if num := 10; num % 2 == 0 { //checks if number is even
        fmt.Println(num,"is even") 
    }  else {
        fmt.Println(num,"is odd")
    }
}
# 如果在if语句中定义一个变量,那么在if语句结束之后,这个变量就会被销毁无法继续访问。
2.switch分支语句

switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上直下逐一测试,直到匹配为止。switch 语句执行的过程从上至下,直到找到匹配项,匹配项后面也不需要再加break。

# 如果switch没有表达式,它会匹配true

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

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var grade string = "B"
   var marks int = 90

   switch marks {
      case 90: grade = "A"
      case 80: grade = "B"
      case 50,60,70 : grade = "C"  //case 后可以由多个数值
      default: grade = "D"  
   }

   switch {
      case grade == "A" :
         fmt.Printf("优秀!\n" )     
      case grade == "B", grade == "C" :
         fmt.Printf("良好\n" )      
      case grade == "D" :
         fmt.Printf("及格\n" )      
      case grade == "F":
         fmt.Printf("不及格\n" )
      default:
         fmt.Printf("差\n" );
   }
   fmt.Printf("你的等级是 %s\n", grade );      
}
# 同样的switch语句当中也可以定义变量,但是其作用域只在switch中,超出作用域就无法使用
fallthrough

Go里面的switch默认每个case最后带有break,匹配成功后不会向下执行其他case,而是跳出整个switch,可以通过fallthrough

强制执行后面的case代码(无需匹配强制执行,仅一行)。

package main

import (
    "fmt"
)

type data [2]int

func main() {
    switch x := 5; x {
    default:
        fmt.Println(x)
    case 5:
        x += 10
        fmt.Println(x)
        fallthrough
    case 6:
        x += 20
        fmt.Println(x)

    }

}
3.for循环语句
for init; condition; post { }

初始化语句只执行一次。在初始化循环之后,将检查该条件。如果条件计算为true,那么{}中的循环体将被执行,然后是post语句。post语句将在循环的每次成功迭代之后执行。在执行post语句之后,该条件将被重新检查。如果它是正确的,循环将继续执行,否则循环终止。

package main

import (  
    "fmt"
)

func main() {  
    for i := 1; i <= 10; i++ {
        fmt.Printf(" %d",i)
    }
}
# for循环的三个组成部分,即初始化、条件和post都是可选的。

九九乘法表

func main() {
   for i := 9; i >= 1; i-- {
      for j := i; j >= 1; j-- {
         fmt.Printf("%d * %d = %d\t", i, j, i*j)
      }
      fmt.Println()
   }
}
4.break

break:跳出循环体。break语句用于在结束其正常执行之前突然终止for循环

package main

import (  
    "fmt"
)

func main() {  
    for i := 1; i <= 10; i++ {
        if i > 5 {
            break //loop is terminated if i > 5
        }
        fmt.Printf("%d ", i)
    }
    fmt.Printf("\nline after for loop")
}
5.continue

continue:跳出一次循环。continue语句用于跳过for循环的当前迭代。在continue语句后面的for循环中的所有代码将不会在当前迭代中执行。循环将继续到下一个迭代。

package main

import (  
    "fmt"
)

func main() {  
    for i := 1; i <= 10; i++ {
        if i%2 == 0 {
            continue
        }
        fmt.Printf("%d ", i)
    }
}
6.goto语句

可以无条件地转移到过程中指定的行。

goto label;
..
..
label: statement;
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++     
   }  
}

goto语句还可以统一的去处理一些错误。

       err := firstCheckError()
    if err != nil {
        goto onExit
    }
    err = secondCheckError()
    if err != nil {
        goto onExit
    }
    fmt.Println("done")
    return
onExit:
    fmt.Println(err)
    exitProcess()

4.数组

1.生成随机数
Math.rand包下	通过随机算法获取,一般获取的数时伪随机数
import (

   "fmt"
   "math/rand"
)

func main() {
   for i := 9; i >= 1; i-- {
      num := rand.Intn(10)
      fmt.Println(num)
   }
}

设置种子数(如果不加种子数,那么每次生成的随机数是一样的)

func main() {
   //获取随机数一般会先给其设置一个唯一的种子数,该类型生成的随机数要比无种子时重复率较低
   rand.Seed(time.Now().Unix())
   for i := 9; i >= 1; i-- {
      num := rand.Intn(10)
      fmt.Println(num)
   }
}
如果想取[15,76]之间的值怎么做?	
rand.Intn(n)是取的[0,n)之间的数字,所以这边只需要取rand.Intn(62)+15即可

数组是具有相同类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整形、字符串或者自定义类型。

var variable_name [SIZE] variable_type
var balance [10] float32
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
# len(balance) //容器中实际存储的数据量
# cap(balance) //容器中能够存储的最大数据量
	因为数组是定长的,所以两个方法的返回值相同
	这两个函数并不是数组所特有的,map、slice、string都有这个函数
var balance = []float32{1000.0, 2.0, 3.4, 7.0, 50.0}
  var a [4] float32 // 等价于:var arr2 = [4]float32{}
  fmt.Println(a) // [0 0 0 0]
  var b = [5] string{"ruby", "王二狗", "rose"}
  fmt.Println(b) // [ruby 王二狗 rose  ]
  var c = [5] int{'A', 'B', 'C', 'D', 'E'} // byte
  fmt.Println(c) // [65 66 67 68 69]
  d := [...] int{1,2,3,4,5}// 根据元素的个数,设置数组的大小
  fmt.Println(d)//[1 2 3 4 5]
  //直接按位置对其进行赋值
  e := [5] int{4: 100} // [0 0 0 0 100]
  fmt.Println(e)
  f := [...] int{0: 1, 4: 1, 9: 1} // [1 0 0 0 1 0 0 0 0 1]
  fmt.Println(f)
遍历数组
package main

import "fmt"

func main() {  
    a := [...]float64{67.7, 89.8, 21, 78}
    for i := 0; i < len(a); i++ { //looping from 0 to the length of the array
        fmt.Printf("%d th element of a is %.2f\n", i, a[i])
    }
}

使用for...range遍历

package main

import "fmt"

func main() {  
    a := [...]float64{67.7, 89.8, 21, 78}
    sum := float64(0)
    for i, v := range a {//range returns both the index and value
        fmt.Printf("%d the element of a is %.2f\n", i, v)
        sum += v
    }
    fmt.Println("\nsum of all elements of a",sum)
}

如果您只需要值并希望忽略索引,那么可以通过使用_ blank标识符替换索引来实现这一点。

for _, v := range a { //ignores index  
}
数组类型:
	数组类型为长度+类型
# 值类型:理解为存储数值的本身
	将数据传递给其他的变量,传递的是数据的副本(备份)
	int,float,string,bool,array
	值类型可以直接比较值是否相等
# 引用类型:理解为存储数据的内存地址
	slice,map。。

数组冒泡排序

func main() {

   var arr = [10]int{10,8,7,4,2,3,5,6,1,9}
   for i:= 1; i < len(arr); i++{
      for j:= 0; j < len(arr)-i; j++{
         if arr[j] > arr[j+1]{
            arr[j], arr[j+1] = arr[j+1], arr[j]
         }
      }
   }
   fmt.Println(arr)
}
3.切片Slice

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

var identifier []type
[5] int 数组 []int 切片	带有长度的是数组,没有长度的是切片

使用make()函数来创建切片:

var slice1 []type = make([]type, len)也可以简写为slice1 := make([]type, len)
append
//后面两个参数分别为其长度和容量s3:make([]int, 3, 8)s[3] = 6 //panic 这边会报错,为什么那?	虽然s3的容量是8,但此时只有3个元素,根本获取不到3这个位置的索引,所以无法操作,正确做法是append函数//注意:添加完后要将返回值赋给s3s3 = append(s3, 6)
# 如果一个切片想要追加另一个切片,记得要在另一个切片后加...	s3 = append(s3,s4...)

for...range同样适用于切片

for _, v := range s3 {   fmt.Println(v)}
扩容
# 每一个切片引用了一个底层数组# 切片本身不存储任何数据,都是这个底层数组存储,修改切片也就是修改这个数组中的数据# 当切片中添加数据时,如果没有超过容量直接添加,如果超过容量,就会进行扩容(成倍增长)# 切片一旦扩容,就是重新指向一个新的底层数组
# 注意:	获取切片地址fmt.printf("%p\n", s3);	# 这边一定不可以加&符号,如果加&符号,那么获取的时切片本身地址,就不是存储的数组地址了
利用数组创建切片
slice = arr[startIndex, endIndex]

将arr从下标startIndex到endIndex的元素创建为一个新的切片(前闭后开),长度为endIndex-startIndex

package main

import "fmt"

func main() {

   arr := [10] int{1,2,3,4,5,6,7,8,9,10}
   s1 := arr[:5]
   s2 := arr[3:8]
   s3 := arr[5:]
   s4 := arr[:]
   fmt.Println(arr)
   fmt.Println(s1)
   fmt.Println(s2)
   fmt.Println(s3)
   fmt.Println(s4)
   //0xc0000160f0
   fmt.Printf("%p\n",s1)
   //0xc0000160f0
   fmt.Printf("%p\n",&arr)

   //长度和容量
   //s1 len:5,cap:10
   fmt.Printf("s1 len:%d,cap:%d\n", len(s1),cap(s1))
   //s2 len:5,cap:7
   fmt.Printf("s2 len:%d,cap:%d\n", len(s2),cap(s2))
   //s3 len:5,cap:5
   fmt.Printf("s3 len:%d,cap:%d\n", len(s3),cap(s3))
   //s4 len:10,cap:10
   fmt.Printf("s4 len:%d,cap:%d\n", len(s4),cap(s4))
}
# 上面切片长度和容量不同的原因是因为,不同的切片指向的数组都是arr,它们的开始索引是不同的,但是结束的索引是相同的,通过这个算法,可以获得切片的长度和容量。
# 数组该片会影响切片的内容,同样的切片改变也会改变数组的值。
深拷贝和浅拷贝
# 深拷贝:	拷贝的是数据本身,值类型的数据都是深拷贝# 浅拷贝:	拷贝的是数据地址	导致多个变量指向同一块内存,引用类型的数据都是浅拷贝# 那么怎么才能让引用类型的数据实现深拷贝那?
s1 := []int{1,2,3}
s2 := make([]int, 0, 0)
for _, v := range s1 {
   s2 = append(s2, v)
}
fmt.Println(s1)
fmt.Println(s2)
s2[0] = 3
fmt.Println(s1)
fmt.Println(s2)
copy
s1 := []int{1,2,3}
s2 := []int{4,5,6,7}
//s1:目标,s2:源,从头开始复制,s1长度不会改变
copy(s1,s2)

fmt.Println(s1)
fmt.Println(s2)
//copy函数可以改变索引去复制copy(s1[1:],s2[2:])

5.Map

map是Go中的内置类型,它将一个值与一个键关联起来。可以使用相应的键检索值。

Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的,也是引用类型

# 使用map过程中需要注意的几点:    map是无序的,每次打印出来的map都会不一样,它不能通过index获取,而必须通过key获取    map的长度是不固定的,也就是和slice一样,也是一种引用类型    内置的len函数同样适用于map,返回map拥有的key的数量    map的key可以是所有可比较的类型,如布尔型、整数型、浮点型、复杂型、字符串型……也可以键。

可以使用内建函数 make 也可以使用 map 关键字来定义 Map:

/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type
//var map1 map[int] string  没有初始化 这个map为nil
	
/* 使用 make 函数 */
map_variable = make(map[key_data_type]value_data_type)
//var map1 = make(map[int]string)
rating := map[string]float32 {"C":5, "Go":4.5, "Python":4.5, "C++":2 }
# 如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对
# 通过key获取value值,如果key不存在,那么获取的是value的默认值
//前面是获取的值,后面的是判断key是否存在
v1,ok := rating["C"]
删除数据

delete函数

//第一个为map,第二个为要删除的key
delete(rating,"C")
map遍历

map可以用for...range函数进行遍历,但是遍历结果是无序的

rating := map[string]float32 {"C":5, "Go":4.5, "Python":4.5, "C++":2 }
for k, v := range rating {
   fmt.Println(k,v)
}

按顺序进行打印

func main() {


   rating := map[int]string {2:"白骨精",1:"红孩儿",3:"牛魔王"}
   for k, v := range rating {
      fmt.Println(k,v)
   }
   //获取key切片
   key := make([]int, 0,len(rating))
   for k, _ := range rating {
      key = append(key,k)
   }
   //使用sort包进行排序
   sort.Ints(key)
   for _, v := range key {
      fmt.Println(v, rating[v])
   }

}

6.String

Go中的字符串是一个字节的切片。可以通过将其内容封装在“”中来创建字符串。Go中的字符串是Unicode兼容的,并且是UTF-8编码的。

# 字符串为一些字节的集合	理解为一个字符的序列	每个字符都有固定的位置(索引,下标,index:从0开始到长度减一)
package main

import (  
    "fmt"
)

func main() {  
    name := "Hello World"
    for i:= 0; i < len(s); i++ {
        fmt.Printf("%d ", s[i])
    }
    fmt.Printf("\n")
    for i:= 0; i < len(s); i++ {
        fmt.Printf("%c ",s[i])
    }
}
func main() {

   slice1 :=[] byte{65, 66, 67, 68}
   s1 := string(slice1) //根据一个字节切片创建字符串
   fmt.Println(s1)

   s2 := "hello world"
   slice2 := []byte(s2) //将字符串转换为一个字节切片
   fmt.Println(slice2)

}
strings

对string的操作,在go中都在strings这个包下

//判断是否存在substr
fmt.Println(strings.Contains(s2, "hel"))
//判断是否存在substr,包含一个字节即可
fmt.Println(strings.ContainsAny(s2, "abco"))
//以***结尾
strings.HasSuffix()
//以***开头
strings.HasPrefix()
//查找substr第一个出现的索引,不存在返回-1
strings.Index()
//查找substr第任意一个最早出现的索引,不存在返回-1
strings.IndexAny()

//字符串拼接
	ss1 := []string{"abc","def"}
	ss2:= strings.Join(ss1, "*")
	fmt.Println(ss2)
//字符串切割
ss3:= strings.Split(ss2,"*")
	fmt.Println(ss3)
//字符串重复5此
ss4 := strings.Repeat("hello",5)
	fmt.Println(ss4)

//字符串替换 第二个参数为 old字符 三个参数为new字符 4个参数为替换几次,如果设为-1表示全部替换
strings.Replace(ss4,"h","*",5)

//全部字符小写
strings.ToLower()
//全部字符大写
strings.ToUpper()

//截取字符串
str[start, end]
strconv
//字符串转换为整型
atoi, err := strconv.Atoi("56")
//整型转为字符串
atoi, err := strconv.Itoa("56")

Parse:字符串解析为其他类型
Formate:其他类型转为字符串

3.函数

函数是执行特定任务的代码块。

# go语言至少有一个main函数
  • func:函数由 func 开始声明
  • funcName:函数名称,函数名和参数列表一起构成了函数签名。
  • parametername type:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
  • output1 type1, output2 type2:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
  • 上面返回值声明了两个变量output1和output2,如果你不想声明也可以,直接就两个类型。
  • 如果只有一个返回值且不声明返回值变量,那么你可以省略包括返回值的括号(即一个返回值可以不声明返回类型)
  • 函数体:函数定义的代码集合。
func funcName(parametername type1, parametername type2) (output1 type1, output2 type2) {
//这里是处理逻辑代码
//返回多个值
return value1, value2
}

1.参数

形式参数:定义函数时,用于接收外部传入的数据,叫做形式参数,简称形参。
实际参数:调用函数时,传给形参的实际的数据,叫做实际参数,简称实参。

func main() {
   getAdd(2,3)
}

func getAdd(a,b int)  {
   fmt.Printf("%d+%d=%d",a,b,a+b);

}
func getAdd(a,b int, string c)  {
   fmt.Printf("%d+%d=%d,%s",a,b,a+b,c);

}
可变参数
//[]int 由结果可知,可变参就是一个切片
func myfunc(nums ...int) {}
s1 := []int{1,3,5,7,9}
myfunc(s1...)//在切片后面加...可以获取里面的数据

func myfunc(nums ...int) {
   sum := 0
   for _, num:= range nums{
      sum+=num
   }
   fmt.Println(sum)
}}
# 注意事项
	1.如果一个函数中除了可变参数还有其他参数,可变参数要放到函数列表最后
	2.一个函数中最多可有一个可变参数
参数传递

Go语言的参数也存在值传递和引用传递

值传递:

func main()  {
   arr1 := [4]int{1,2,3,4}
   fmt.Println("函数调用前数组数据:",arr1)
   fun1(arr1)
   fmt.Println("函数调用后数组数据:",arr1)

}

func fun1(arr1 [4]int)  {
   fmt.Println("函数中,数组的数据",arr1)
   arr1[2] = 100
   fmt.Println("函数中,数组修改的数据",arr1)
}

引用传递:

# 传递的是数据的地址,导致多个变量指向同一块内存

2.返回值

一个函数被调用后,返回给调用处的执行结果,叫做函数的返回值。调用处需要使用变量接收该结果

func myfunc(num int) int{
   sum := 0
   for i:= 0; i < num; i++{
      sum+=i
   }
   return sum
}
//也可以带返回值名称
func myfunc(num int) sum int{
   //这边就可以不定义sum了
   for i:= 0; i < num; i++{
      sum+=i
   }
   //返回值默认就返回sum,当然也可以自己返回
   return 
}
多返回值

一个函数可以没有返回值,也可以有一个返回值,也可以有返回多个值。

package main

import "fmt"

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

func main() {
   a, b := swap("Mahesh", "Kumar")
   fmt.Println(a, b)
}
func SumAndProduct(A, B int) (add int, Multiplied int) {
    add = A+B
    Multiplied = A*B
    return
}
空白标识符

_是Go中的空白标识符。它可以代替任何类型的任何值。让我们看看这个空白标识符的用法。

比如rectProps函数返回的结果是面积和周长,如果我们只要面积,不要周长,就可以使用空白标识符。

package main

import (  
    "fmt"
)

func rectProps(length, width float64) (float64, float64) {  
    var area = length * width
    var perimeter = (length + width) * 2
    return area, perimeter
}
func main() {  
    area, _ := rectProps(10.8, 5.6) // perimeter is discarded
    fmt.Printf("Area %f ", area)
}
return
# return作用:	1.一个函数有返回值,使用return会将返回值返回给调用出	2.意味着整个函数的结束#注意:如果以个函数定义了返回值,必须使用return将结果返回给调用出,return后的数据必须和定义的一致:个数、类型、顺序	  可以使用空白标识符_来舍弃返回值	  如果函数中含有分支,那么要保证无论哪个分支,都要有return语句被执行	  如果一个函数没有定义返回值,那么这个函数也可以使用return,用于结束函数的运行

3.变量作用域

局部变量

一个函数内部定义的变量,就叫做局部变量

变量在哪里定义,就只能在哪个范围使用,超出这个范围,我们认为变量就被销毁了。

全局变量

一个函数外部定义的变量,就叫做全局变量

所有的函数都可以使用,而且共享这一份数据

# 全局变量不支持简短定义的方式

4.递归函数

# 递归函数(recursion):一个函数自己调用自己,就叫递归函数。	递归函数要有一个出口,且逐渐向出口靠近
func main()  {
    //计算1-5的和
   fmt.Println(getSum(5))
}

func getSum(num int) int {
   if num == 1{
      return 1
   }
   return getSum(num-1) + num
}

5.defer语句

即延迟(defer)语句,延迟语句被用于执行一个函数调用,在这个函数之前,延迟语句返回。比如说我们在主函数中调用了一个函数,但是这个函数被defer修饰了,那么这个函数就不执行了,等到主函数的所有代码都执行结束之后,才会执行这个函数。

# defer用法	1.对象close的时候,临时文件的删除		文件.open()		defer close()		读或写操作	2.go语言中关于异常的处理,使用recover和panic	panic函数引发恐慌,导致中断程序	recover 函数用于恢复执行,同时recover必须在defer中执行
    如果有很多调用defer,那么defer是采用后进先出模式    在离开所在的方法时,执行(报错的时候也会执行)
# defer在传递参数的时候	defer在函数调用的时候就已经传递参数了,	只是暂时不执行函数中的代码而已
# defer函数特点:    当外围函数中的语句正常执行完毕时,只有其中所有的延迟函数都执行完毕,外围函数才会真正的结束执行。    当执行外围函数中的return语句时,只有其中所有的延迟函数都执行完毕后,外围函数才会真正返回。    当外围函数中的代码引发运行恐慌时,只有其中所有的延迟函数都执行完毕后,该运行时恐慌才会真正被扩展至调用函数。

6.数据类型

在Go语言中,函数也是一种数据类型。

 # 函数作为一种复合数据类型,可以看做是一种特殊的变量。                函数名():将函数进行调用,函数中的代码会全部执行,然后将return的结果返回给调用处                函数名:指向函数体的内存地址
//直接定义一个函数类型的变量var fun1 func(int int)//<nil>fmt.Println(fun1)func main()  {	var fun1 func(int int)	//<nil>	fmt.Println(fun1)	//将getSum的内存地址赋值给fun1	fun1 = getSum}func getSum(num1 int)  {}

7.匿名函数

匿名函数:没有名字的函数。

定义一个匿名函数,直接进行调用。通常只能使用一次。也可以使用匿名函数赋值给某个函数变量,那么就可以调用多次了。

package main

import "fmt"

func main() {
    /*
    匿名:没有名字
        匿名函数:没有名字的函数。

    定义一个匿名函数,直接进行调用。通常只能使用一次。也可以使用匿名函数赋值给某个函数变量,那么就可以调用多次了。

    匿名函数:
        Go语言是支持函数式编程:
        1.将匿名函数作为另一个函数的参数,回调函数
        2.将匿名函数作为另一个函数的返回值,可以形成闭包结构。
     */
     fun1()
     fun1()
     fun2 := fun1
     fun2()

     //匿名函数
     func (){
        fmt.Println("我是一个匿名函数。。")
     }()

     fun3:=func(){
        fmt.Println("我也是一个匿名函数。。")
     }
     fun3()
     fun3()

     //定义带参数的匿名函数
     func (a,b int){
        fmt.Println(a,b)
     }(1,2)

     //定义带返回值的匿名函数
     res1 := func (a, b int)int{
        return a + b
     }(10,20) //匿名函数调用了,将执行结果给res1
     fmt.Println(res1)

     res2 := func (a,b int)int{
        return a + b
     } //将匿名函数的值,赋值给res2
     fmt.Println(res2)

     fmt.Println(res2(100,200))
}

func fun1(){
    fmt.Println("我是fun1()函数。。")
}
回调函数

高阶函数:根据go语言的数据类型的特点,可以将一个函数作为另一个函数的参数

# fun1(),fun2()	将fun1函数作为fun2函数的参数	fun2函数就被称为高阶函数:		接受了一个函数作为参数,高阶函数			fun1函数:回调函数		作为了一个函数的参数,回调函数
package main

import "fmt"

func main() {
    /*
    高阶函数:
        根据go语言的数据类型的特点,可以将一个函数作为另一个函数的参数。

    fun1(),fun2()
    将fun1函数作为了fun2这个函数的参数。

            fun2函数:就叫高阶函数
                接收了一个函数作为参数的函数,高阶函数

            fun1函数:回调函数
                作为另一个函数的参数的函数,叫做回调函数。
     */
    //设计一个函数,用于求两个整数的加减乘除运算
    fmt.Printf("%T\n", add)  //func(int, int) int
    fmt.Printf("%T\n", oper) //func(int, int, func(int, int) int) int

    res1 := add(1, 2)
    fmt.Println(res1)

    res2 := oper(10, 20, add)
    fmt.Println(res2)

    res3 := oper(5,2,sub)
    fmt.Println(res3)

    fun1:=func(a,b int)int{
        return a * b
    }

    res4:=oper(10,4,fun1)
    fmt.Println(res4)

    res5 := oper(100,8,func(a,b int)int{
        if b == 0{
            fmt.Println("除数不能为零")
            return 0
        }
        return a / b
    })
    fmt.Println(res5)

}
func oper(a, b int, fun func(int, int) int) int {
    fmt.Println(a, b, fun) //打印3个参数
    res := fun(a, b)
    return res
}

//加法运算
func add(a, b int) int {
    return a + b
}

//减法
func sub(a, b int) int {
    return a - b
}
闭包

一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量(外层函数中的参数,或者外层函数中直接定义的变量),并且该外层函数的返回值就是这个内层函数。

这个内层函数和外层函数的局部变量,统称为闭包结构。

package main

import "fmt"

func main() {
    /*
    go语言支持函数式编程:
        支持将一个函数作为另一个函数的参数,
        也支持将一个函数作为另一个函数的返回值。

    闭包(closure):
        一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量(外层函数中的参数,或者外层函数中直接定义的变量),并且该外层函数的返回值就是这个内层函数。

        这个内层函数和外层函数的局部变量,统称为闭包结构。

        局部变量的生命周期会发生改变,正常的局部变量随着函数调用而创建,随着函数的结束而销毁。
        但是闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还要继续使用。

     */
     res1 := increment() //res1 = fun
     fmt.Printf("%T\n",res1) //func() int
     fmt.Println(res1)
     v1 := res1()
     fmt.Println(v1) //1
     v2 := res1()
     fmt.Println(v2) //2
     fmt.Println(res1())
     fmt.Println(res1())
     fmt.Println(res1())
     fmt.Println(res1())

    
    //这边又会重新建立新的内存空间,之后的操作就和前面的无关了。
     res2 := increment()
     fmt.Println(res2)
     v3 :=res2()
     fmt.Println(v3) //1
     fmt.Println(res2())

     fmt.Println(res1())
}

func increment()func()int{ //外层函数
    //1.定义了一个局部变量
    i := 0
    //2.定义了一个匿名函数,给变量自增并返回
    fun := func ()int{ //内层函数
        i++
        return i
    }
    //3.返回该匿名函数
    return fun
}

4.指针

指针是存储另一个变量的内存地址的变量。

在上面的图中,变量b的值为156,存储在内存地址0x1040a124。变量a持有b的地址,现在a被认为指向b。

# 声明指针,*T是指针变量的类型,它指向T类型的值。
var var_name *var-typevar ip *int        /* 指向整型*/var fp *float32    /* 指向浮点型 */
func main()  {    //定义一个int类型的变量   a := 10       //创建一个指针,用于存储变量a的地址   var ip *int   ip = &a   fmt.Println(a)   fmt.Println(&a)   fmt.Println(ip)        //获取这个地址存储的数据   fmt.Println(*ip)}

1.指针的指针

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

var ptr **int;
package mainimport "fmt"func main() {   var a int   var ptr *int   var pptr **int   a = 3000   /* 指针 ptr 地址 */   ptr = &a   /* 指向指针 ptr 地址 */   pptr = &ptr   /* 获取 pptr 的值 */   fmt.Printf("变量 a = %d\n", a )   fmt.Printf("指针变量 *ptr = %d\n", *ptr )   fmt.Printf("指向指针的指针变量 **pptr = %d\n", **pptr)}

2.数组指针和指针数组

数组指针:是一个指针,指向一个数组的地址

指针数组:是一个数组,存储的类型是指针

//数组指针func main()  {   //创建一个普通数组   var arr1 [4] int   arr1 = [4]int{1,2,3,4}   //创建一个数组指针   var p1 *[4]int   p1 = &arr1   //&[1 2 3 4] 表示这是一个数组指针   fmt.Println(p1)   fmt.Printf("%p\n",p1) //arr1的地址   fmt.Printf("%p\n",&p1)//p1自己的地址   //根据数组指针操作数组   (*p1)[0] = 100   fmt.Println(arr1)   //简化写法   p1[0] = 200   fmt.Println(arr1)}
//指针数组a := 1	b := 1	c := 1	d := 1	arr2 := [4]int{a,b,c,d}	arr3 := [4]*int{&a,&b,&c,&d}	fmt.Println(arr2)	fmt.Println(arr3)	//这边操作不会影响a的值,只会修改arr2的值	arr2[0] = 200	fmt.Println(a)	*arr3[0] = 200	fmt.Println(a)

3.函数指针和指针函数

函数指针:一个指针,指向了一个函数的地址

指针函数:一个函数,该函数的返回值是一个指针

4.指针作为参数

# 切片就没有必要定义指针了,因为切片本身就是引用类型的数据。

5.结构体

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

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

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

一旦定义了结构体类型,它就能用于变量的声明

variable_name := structure_variable_type {value1, value2...valuen}
// 1.按照顺序提供初始化值P := person{"Tom", 25}// 2.通过field:value的方式初始化,这样可以任意顺序P := person{age:24, name:"Tom"}// 3.new方式,未设置初始值的,会赋予类型的默认初始值p := new(person)p.age=24
func main() {   var per Person   //输出{ 0 }这是因为per没有初始化,只打印了它的初始值   fmt.Println(per)   per.name = "gewei"   per.age = 24   per.sex = "男"   fmt.Println(per)        //方法2	p2 := Person{}}type Person struct {   name string   age int   sex string}

1. make,new

make用于自建类型(map、slice和channel)的内存分配,new用于各种类型的内存分配。内建函数new本质上说与其他语言中同名函数功能一样,new(T)分配了零值填充的T类型的内存空间,并返回其内存地址,即一个*T类型的值。用Go的术语说,他返回了一个指针,指向新分配类型T的零值。有一点非常重要:new返回指针

# 内建函数make(T,args)与new(T)有着不同的功能,make只创建了slice、map和channel,并且返回了一个有初始值,非零值的(T)类型,而不是*T。
# 结构体是值类型的数据	那么如何实现深拷贝那?	这边可以通过指针去操作。
func main() {   //使用内置函数new,用于创建某种类型的指针函数   p1 := new(Person)   p1.age = 18   p1.name = "gewei"   p1.sex = "nan"   //&{gewei 18 nan}这里表示p1为一个结构体的指针   fmt.Println(p1)}type Person struct {   name string   age int   sex string}

2.匿名结构体和匿名字段

//匿名结构体p2 := struct {   name string   age  int}{   "zhangsan",   17,}//匿名字段,字段类型不可以重复type Worker struct {	string	int}w2 := Worker{"lisai",32}fmt.Println(w2.string)fmt.Println(w2.int)
# 匿名结构体:没有名字的结构体,在创建结构体的过程中同时创建对象# 匿名字段:一个结构体的字段没有字段名

3.结构体嵌套

type Human struct {
    name string
    age int
    weight int
} 
type Student struct {
    Human // 匿名字段,那么默认Student就包含了Human的所有字段
    speciality string
} 
func main() {
    // 我们初始化一个学生
    mark := Student{Human{"Mark", 25, 120}, "Computer Science"}
    // 我们访问相应的字段
    fmt.Println("His name is ", mark.name)
    fmt.Println("His age is ", mark.age)
    fmt.Println("His weight is ", mark.weight)
    fmt.Println("His speciality is ", mark.speciality)
    // 修改对应的备注信息
    mark.speciality = "AI"
    fmt.Println("Mark changed his speciality")
    fmt.Println("His speciality is ", mark.speciality)
    // 修改他的年龄信息
    fmt.Println("Mark become old")
    mark.age = 46
    fmt.Println("His age is", mark.age)
    // 修改他的体重信息
    fmt.Println("Mark is not an athlet anymore")
    mark.weight += 60
    fmt.Println("His weight is", mark.weight)
}
# 如果一个结构体中包含另一个结构体,那么这个结构体可以调用另一个结构体的字段(直接访问),这个字段也称为提升字段。
	需要注意的是,包含的那个结构体必须是匿名字段。

6.方法

Go 语言中同时有函数和方法。一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集

//方法前面必须加接受者类型
func (t Type) methodName(parameter list)(return list) {

}
func funcName(parameter list)(return list){

}
func main() {
   var p1 Person
   p1.name = "小二黑"
   p1.work()
}

type Person struct {
   name string
   age int
   sex string
}

func (person Person)work()  {
   fmt.Println(person.name+"在工作" )
}
# 方法可以被结构体类型或其指针调用
	方法和结构体的区别:
		方法:某个类别的行为,需要特定的接收者使用
		函数:一段独立的代码,可以直接调用
	语法:
		方法:方法名可以相同,只要接收者不同就可以了
		函数:明名不可以冲突

1.继承

# 继承:    1.子类可以访问父类的属性和方法    2.子类可以新增自己的属性和方法    3.子类可以重写父类的方法

package main

import "fmt"

func main() {
   p1 := Person{"二狗",66}
   fmt.Println(p1.name,p1.age) //父类对象,访问父类对象属性
   p1.eat() //父类对象访问父类方法
   //创建student类型
   s1 := Student{Person{"小王",13},"春田花花"}
   fmt.Println(s1.name)//子类对象通过匿名字段的方式直接访问父类的属性
   s1.eat() //子类对象访问父类对象的方法

}

type Person struct {
   name string
   age int
}

type Student struct {
   //结构体嵌套,模拟继承性
   Person
   school string
}

//方法
func (p Person) eat() {
   fmt.Println("父类的方法,吃窝窝头")
}

//子类新增的方法

func (s Student) study(){
   fmt.Println("子类新增的方法")
}
//子类重写的方法
func (s Student) eat() {
   fmt.Println("子类重写的方法,吃大馒头")
}

7.接口

面向对象世界中接口的一般定义为“接口定义对象的行为”。它表示对象应该做什么。

在Go中,接口是一组方法签名。当类型为接口中的所有方法提供定义时,它被称为实现接口。它与OOP非常相似。接口指定了类型应该具有的方法,类型决定了如何实现这些方法。

# 它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口

# 接口定义了一组方法,如果某个对象实现了某个接口的所有方法,则此对象就实现了该接口。
/* 定义接口 */
type interface_name interface {
   method_name1 [return_type]
   method_name2 [return_type]
   method_name3 [return_type]
   ...
   method_namen [return_type]
}

/* 定义结构体 */
type struct_name struct {
   /* variables */
}

/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
   /* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
   /* 方法实现*/
}
# 在go语言中,接口和类型的实现关系是非嵌入式的,你只需要实现方法即可,并不需要显示的书写出来。
package main

import (
    "fmt"
)

type Phone interface {
    call()
}

type NokiaPhone struct {
}

func (nokiaPhone NokiaPhone) call() {
    fmt.Println("I am Nokia, I can call you!")
}

type IPhone struct {
}

func (iPhone IPhone) call() {
    fmt.Println("I am iPhone, I can call you!")
}

func main() {
    var phone Phone

    phone = new(NokiaPhone)
    phone.call()

    phone = new(IPhone)
    phone.call()

}
  • interface可以被任意的对象实现
  • 一个对象可以实现任意多个interface
  • 任意的类型都实现了空interface(我们这样定义:interface{}),也就是包含0个method的interface
# 接口对象不可以访问实现类中的属性。
# go语言通过接口来模拟多态
	1.看成实现本身的类型,能够访问实现类中的属性和方法
	2.看成对应的接口类型,只能够访问接口中的方法
	
# 接口的用法:
	如果一个函数接收接口类型作为参数,那么实际上可以传入该类型的任意实现类作为参数。

1.空接口

package main

import "fmt"

func main() {
	/*
	空接口(interface{})
		不包含任何的方法,正因为如此,所有的类型都实现了空接口,因此空接口可以存储任意类型的数值。
	fmt包下的Print系列函数:
		func Print(a ...interface{}) (n int, err error)
		func Printf(format string, a ...interface{}) (n int, err error)
		func Println(a ...interface{}) (n int, err error)
	 */
	var a1 A = Cat{"花猫"}
	var a2 A = Person{"王二狗",30}
	var a3 A = "haha"
	var a4 A = 100
	fmt.Println(a1)
	fmt.Println(a2)
	fmt.Println(a3)
	fmt.Println(a4)
	test1(a1)
	test1(a2)
	test1(3.14)
	test1("Ruby")

	test2(a3)
	test2(1000)

	//map,key字符串,value任意类型
	map1 := make(map[string]interface{})
	map1["name"] = "李小花"
	map1["age"] = 30
	map1["friend"] = Person{"Jerry",18}
	fmt.Println(map1)

	//切片,存储任意类型的数据
	slice1 := make([]interface{},0,10)
	slice1 = append(slice1,a1,a2,a3,a4,100,"abc")
	fmt.Println(slice1)

	test3(slice1)

}

func test3(slice2 []interface{}){
	for i:=0;i<len(slice2);i++{
		fmt.Printf("第%d个数据:%v\n",i+1,slice2[i])
	}
}

//接口A是空接口,理解为代表了任意类型
func test1(a A){
	fmt.Println(a)
}

func test2(a interface{}){
	fmt.Println("--->",a)
}

//空接口
type A interface {

}
type Cat struct {
	color string
}
type Person struct {
	name string
	age int
}

2.接口嵌套

package main

import "fmt"

func main() {
	/*
	接口的嵌套:
	 */
	var cat Cat = Cat{}
	cat.test1()
	cat.test2()
	cat.test3()

	fmt.Println("--------------")
	var a1 A = cat
	a1.test1()

	fmt.Println("--------------")
	var b1 B = cat
	b1.test2()
	fmt.Println("--------------")
	var c1 C = cat
	c1.test1()
	c1.test2()
	c1.test3()

	fmt.Println("----------")
	//var c2 C = a1
	var a2 A = c1
	a2.test1()
}

type A interface {
	test1()
}

type B interface {
	test2()
}

type C interface {
	A
	B
	test3()
}

type Cat struct {
	//如果想实现接口C,那不止要实现接口C的方法,还要实现接口A,B中的方法
}

func (c Cat) test1() {
	fmt.Println("test1()....")
}

func (c Cat) test2() {
	fmt.Println("test2()....")
}

func (c Cat) test3() {
	fmt.Println("test3()....")
}

3.接口断言

// 安全类型断言

<目标类型的值>,<布尔参数> := <表达式>.( 目标类型 )

//非安全类型断言

<目标类型的值> := <表达式>.( 目标类型 )
package main

import (
	"math"
	"fmt"
)

func main() {
	/*
	接口断言:
		方式一:
			1.instance := 接口对象.(实际类型) //不安全,会panic()
			2.instance, ok := 接口对象.(实际类型) //安全
		方式二:switch
			switch instance := 接口对象.(type){
			case 实际类型1:
					....
			case 实际类型2:
					....
			....
			}
	 */
	 var t1 Triangle = Triangle{3,4,5}
	 fmt.Println(t1.peri())
	 fmt.Println(t1.area())
	 fmt.Println(t1.a, t1.b,t1.c)

	 var c1 Circle = Circle{4}
	 fmt.Println(c1.peri())
	 fmt.Println(c1.area())
	 fmt.Println(c1.radius)

	 var s1 Shape
	 s1 = t1
	 fmt.Println(s1.peri())
	 fmt.Println(s1.area())

	 var s2 Shape
	 s2 = c1
	 fmt.Println(s2.peri())
	 fmt.Println(s2.area())

	 testShape(t1)
	 testShape(c1)
	 testShape(s1)

	 getType(t1)
	 getType(c1)
	 getType(s1)
	 //getType(100)

	 var t2 *Triangle = &Triangle{3,4,2}
	 fmt.Printf("t2:%T,%p,%p\n",t2,&t2,t2)
	 getType(t2)
	 getType2(t2)
	 getType2(t1)

}

func getType2 (s Shape){
	switch ins := s.(type) {
	case Triangle:
		fmt.Println("三角形。。",ins.a,ins.b,ins.c)
	case Circle:
		fmt.Println("圆形。。",ins.radius)
	case *Triangle:
		fmt.Println("三角形结构体指针:",ins.a,ins.b,ins.c)
	}
}
func getType(s Shape){
	//断言
	if ins, ok := s.(Triangle) ; ok{
		fmt.Println("是三角形,三边是:",ins.a,ins.b,ins.c)
	}else if ins, ok := s.(Circle); ok{
		fmt.Println("是圆形,半径是:",ins.radius)
	}else if ins, ok := s.(*Triangle) ;ok{
		fmt.Printf("ins:%T,%p,%p\n",ins,&ins,ins)
		fmt.Printf("s:%T,%p,%p\n",s,&s,s)
	}else {
		fmt.Println("我也不知道了。。。")

	}
}

func testShape(s Shape){
	fmt.Printf("周长:%.2f,面积:%.2f\n",s.peri(),s.area())
}
//1.定义一个接口
type Shape interface {
	peri() float64 //形状的周长
	area() float64 //形状的面积
}

//2.定义实现类:三角形
type Triangle struct {
	//a float64
	//b float64
	//c float64
	a, b, c float64
}

func (t Triangle) peri() float64  {
	return t.a + t.b + t.c
}

func (t Triangle) area() float64  {
	p := t.peri() / 2
	s := math.Sqrt(p * (p-t.a)*(p-t.b)*(p-t.c))
	return s
}

type Circle struct {
	radius float64
}

func (c Circle) peri()float64  {
	return c.radius * 2 * math.Pi
}
func (c Circle) area () float64{
	return math.Pow(c.radius,2) * math.Pi
}
}
12
6
3 4 5
25.132741228718345
50.26548245743669
4
12
6
25.132741228718345
50.26548245743669
周长:12.00,面积:6.00
周长:25.13,面积:50.27
周长:12.00,面积:6.00
是三角形,三边是: 3 4 5
是圆形,半径是: 4
是三角形,三边是: 3 4 5
t2:*main.Triangle,0xc0000d8020,0xc0000ae0f0
ins:*main.Triangle,0xc0000d8028,0xc0000ae0f0
s:*main.Triangle,0xc000088260,0xc0000ae0f0
三角形结构体指针: 3 4 2
三角形。。 3 4 5

Q.E.D.


勤俭节约,艰苦奋斗。