zoukankan      html  css  js  c++  java
  • Go语言学习-基础知识

    第一个Go程序

    第一个简单的HelloGo程序hello.go

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	fmt.Println("hello,Go!")
    }
    
    • 定义一个包名位main,main是可执行程序的包名,所有Go源文件必须有一个包声明语句,通过包名管理命名空间
    • import 引入一个外部包 fmt,可以是标准库的包,也可以是第三方自定义的包,fmt是标准输入/输出包
    • func 关键字声明定义一个函数,函数名为main,main代表程序的入口函数
    • 调用fmt包里的Println函数打印一句话

    注意:

    • 源程序以.go结尾
    • 默认为UTF-8编码
    • 标识符区分大小写
    • 语句结尾分号可以忽略不写
    • 函数以func开头,函数体的{必须在函数头所在行尾部,不能单独起一行
    • 调用包里方法通过.访问符
    • main函数所在的包名必须是main

    标识符和关键字

    标识符用来定义变量、类型、常量等语法对象的符号名称,不能使用语言预留的标识符,会导致歧义

    Go的标识符构规则:标识符由若干个字母、下划线_、和数字组成,且第一个字符必须是字母。字母区分大小写,Unicode可以作为标识符构成,但不建议这样使用。不能包含空格。
    注意:

    • 标识符的命名要尽量采取简短且有意义;

    • 不能和标准库中的包名重复;

    • 为变量、函数、常量命名时采用驼峰命名法,例如 stuNamegetVal
      在这里插入图片描述

    • 关键字:语言保留的特定语法含义的标识符

    • 数据类型标识符:语言基本的内置类型,用在变量和常量声明,Go是强类型静态编译型语言,可以自动推到类型,但定义新的类型时必须指定类型标识符

    • 常量标识符:代表一个常量值,表达特殊的含义

    • 空白标识符:_用来声明一个匿名的变量,在赋值表达式的左端,空白标识符也通常被用作占位,比如忽略多个返回值中的一个

    • 内置函数:是高级语言的一种语法糖,由于语言内置,不需要使用import来引入,内置函数具有全局可见性

    变量和常量

    通过一个标识符绑定一块内存地址,如果内训可以修改,则为变量,不能修改为常量
    基本类型变量声明:

    1. 显示完整声明

      var 变量名 类型 [ = 值]
      
    • 变量声明后会立即为其分配空间
    • 不指定初始值默认初始化为零值
    1. 短类型声明

      变量名 := 值
      
    • :=这只能出现在

    • Go 编译器会自动进行数据类型推断

    • Go 支持多个类型变量同时声明并赋值
      如:

      a , b := 1 , "hello"
      

    Go 语言提供自动内存管理,不需要关心变量的生存期和存放位置,编译器使用栈逃逸技术能够自动为变量分配空间:可能在栈上,也可能在堆上。

    常量
    常量分为布尔类型、字符串类型、数值型常量,存储在只读段里。
    预声明标识符itoa用在常量声明中,初始值为0。一组多个常量同时声明其值逐行增加,itoa可以看作自增的枚举变量,专门用来初始化常量。

    	//类似枚举的iota
    	const (
    		c0 = iota // c0 == 0
    		c1 = iota // c1 == 1
    		c2 = iota // c2 == 2
    	)
    
    	//简写模式
    	const (
    		c0 = iota // c0 == 0
    		c1        // c1 == 1
    		c2        // c2 == 2
    	)
    
    	//分开的const语句
    	const x = iota // x == 0
    	const y = iota // y == 0
    

    基本数据类型

    布尔型
    关键字bool,取值true和false
    声明的bool变量如果不初始化,默认为false

    var ok bool
    ok = true
    

    或者

    ok := false
    

    bool类型与整数类型不能相互转换
    bool类型用在:比较表达式和逻辑表达式的结果,if和for的条件部分一定是布尔类型的值或表达式

    整型
    不同类型的整形必须进行强制类型转换

    var a int = 1
    var b int32 = 2
    b = a //error
    

    整形支持算术运算和位运算,算术表达式和位操作的结果还是整型

    var a int = (1 + 2) * 3
    var b int = 1000 >> 2
    

    浮点型
    用于表示包含小数数据,有两种类型分别是float32float64
    浮点数自动类型推倒位float64

    var b := 10.00 
    

    计算机很难对浮点型数精确存储,两个浮点数之间不要用==!=进行比较,高精度科学计算使用math

    复数型
    在计算机中使用两个浮点数表示,一个表示实部,一个表示虚部,complex64是由两个float32构成的,complex128是由两个float64构成的。复数的字面量表示和数学表示法一样。

    var v1 complex64 = 3.1 + 5i
    v2 := 3.1 + 6i
    
    //Go有三个函数处理复数
    var v = complex(2.1, 3) //构造一个复数
    a := real(v1)           //返回复数的实部
    b := image(v2)          //返回复数的虚部
    

    字符串类型

    • 字符串初始化可以使用字符串字面常量
    var a = "hello world"
    
    • 字符串是常量,可以通过类似数组索引访问字节单元,但不能修改某字节的值
    var a = "hello,world"
    b := a[0]
    a[1] = 'a' //error
    
    • 字符串转换为切片[]byte(s)时候要慎用,尤其是数据量较大时候,每次转换都需要赋值大量的内容
    a := "hello,world"
    b := []byte(a)
    
    • 字符串末尾不包含NULL字符,和C/C++不一样
    • 字符串底层实现是一个二元数据结构,一个是指向字节数组的起点,另一个是长度,如:
    type stringStruct struct {
    	str unsafe.Pointer	//指向底层字节数组的指针
    	len int	//字节数组的长度
    }
    
    • 基于字符串创建的切片和源字符串指向相同的底层字符串数组,一样不能修改,对字符串的切片操作返回的子串仍然是string,而非slice
    a := "hello,world"
    b := a[0:4]
    c := a[1:]
    d := a[:4]
    
    • 字符串和切片的转换:字符串可以转换为字节数组,也可以转换为Unicode的字数组
    a := "hello,world"
    b := []byte(a)
    c := []rune(a)
    
    • 字符串的运算:拼接、求长度、遍历
    a := "hello"
    b := "world"
    c := a + b	//字符串拼接
    len(a)	//len函数获取字符串的长度
    d := "hello,世界"
    //遍历字节数组
    for i := 0; i<len(d); i++ {
    	fmt.Println(d[i])
    }
    //遍历rune数组
    for i, v :=range d {
    	fmt.Println(i,v)
    }
    

    rune类型
    Go内置两种字符类:1.byte的字节类型;2.表示Unicode编码的字符rune。
    rune在Go内部是int32类型的别名,占用4个字节。
    Go语言默认字符编码就是UTF-8类型的,如果需要特殊的编码转换,使用Unicode/UTF-8标准包。

    复合数据类型

    复合类型是由其他类型组合而成的类型。
    Go的基本符合数据类型有:指针、数组、切片、字典、通道、结构、接口。

    指针
    指针的声明类型为*T, Go同样支持多级指针**T。通过在变量名前面加&来获取变量的地址。

    • 在赋值语句中,T出现在“=”左边表示指针声明,T出现在“=”右边表示取指针指向的值(varName为变量名)。
    var a =ll
    p := &a
    
    • 结构体指针访问结构体字段仍然使用“.”点操作符,Go语言没有“>”操作符。
    	type User struct {
    		name string
    		age  int
    	}
    	andes := User{
    		name: "andes",
    		age:  18,
    	}
    	p := &andes
    	fmt.Println(p.name) //p.name通过“.”操作符访问成员变量
    
    • Go不支持指针的运算。Go由于支持垃圾回收,如果支持指针运算,则会给垃圾回收的实现带来很多不便,在C和C++里面指针运算很容易出现问题,因此Go直接在语言层面禁止指针运算。
    	a := 1234
    	p := &a
    	p++ //不允许,报non-numeric type*int错误
    
    • 函数中允许返回局部变量的地址。
      Go编译器使用“栈逃逸”机制将这种局部变量的空间分配在堆上。
    func sum(a, b int) *int {
    	sum := a + b
    	return &sum //允许,sum会分配在heap上
    }
    

    数组
    数组的类型名是[n]类型,其中n是数组长度。比如一个包含2个int类型元素的数组类型可表示为[2]int。数组一般在创建时通过字面量初始化,单独声明一个数组类型变量而不进行初始化是没有意义的。

    	var arr [2]int                       //声明一个有两个整型的数组,但元素默认值都是0,一般很少这样使用
    	array := [...]float64{7.0, 8.5, 9.1} //[...1后面跟字面量初始化列表
    
    • 数组的初始化
    a := [3]int{1, 2, 3}   //指定长度和初始化字面量
    a := [...]int{1, 2, 3} //不指定长度,但是由后面的初始化列表数量来确定其长度
    a := [3]int{1: 1, 2: 3}   //指定总长度,并通过索引值进行初始化,没有初始化元素时使用类型默认值
    a := [...]int{1: 1, 2: 3} //不指定总长度,通过索引值进行初始化,数组长度由最后一个索引值确定,没有指定索引的元素被初始化为类型的零值
    
    • 数组的特点
      (1)数组创建完长度就固定了,不可以再追加元素。
      (2)数组是值类型的,数组赋值或作为函数参数都是值拷贝。
      (3)数组长度是数组类型的组成部分,[10]int和[20]int表示不同的类型。
      (4)可以根据数组创建切片。
    • 数组元素访问
    a := [...]int{1, 2, 3}
    b := a[0]
    for i,v := range a {
    	//...
    }
    
    • 数组长度
    a :=[...]int{1,2,3}
    alength := len(a)
    for i:=0; i<alength; i++ {
    	//...
    }
    

    切片(slice)
    数组的定长性和值拷贝限制了其使用场景,Go提供了另一种数据类型slice(切片),这是一种变长数组,其数据结构中有指向数组的指针,所以是一种引用类型。
    在Go中,几乎所有场景,可以使用切片代替数组

    ll type slice struct {
    	array unsafe.Pointer
    	len int
    	cap int
    }
    

    Go为切片维护三个元素:指向底层数组的指针、切片的元素数量、底层数组的容量。

    • 切片创建
    1. 由数组创建
      创建语法array [begin:end].
      array表示数组名;
      begin表示开始索引,可以不指定,默认是0;
      end表示结束索引,可以不指定,默认是len(array)。
      array[begin:end]表示一个包含end-begin个元素的切片(切片容量),第一个元素是aray[b],最后一个元素是array[e-1]。
      注意:按数组下标算,左闭右开,表示包含begin下表,不包含end下标
    var array = [...]int{0, 1, 2, 3, 4, 5, 6}
    s1 :=  array[0:4]	//[0 1 2 3]
    s2 := array[:4]	//[0 1 2 3]
    s3 := array[2:]	//[2 3 4 5 6]
    
    1. 内置函数make创建切片
    a := make([]int, 10)	//len = 10, cap = 10, 结果为[0 0 0 0 0 0 0 0 0 0]
    b := make([]int, 10, 15)	//len = 10, cap = 15, 结果为[0 0 0 0 0 0 0 0 0 0]
    
    1. 直接声明切片类型变量
    var a []int	//结果为[]
    

    直接声明切片类型变量是没有意义的,此时切片的底层的数据结构

    • 切片的操作
      内置函数len()返回切片长度。
      内置函数cap()返回切片底层数组容量。
      内置函数append()对切片追加元素。
      内置函数copy()用于复制一个切片。
    	a := [...]int{0, 1, 2, 3, 4, 5, 6}
    	b := make([]int, 2, 4)
    	c := a[0:3]
    	
    	fmt.Println(len(b)) //2
    	fmt.Println(cap(b)) //4
    	
    	b = append(b, 1)
    	fmt.Println(b)      //[0 0 1]
    	fmt.Println(len(b)) //3
    	fmt.Println(cap(b)) //4
    	
    	b = append(b, c...)
    	fmt.Println(b)      //[0 0 1 0 1 2]
    	fmt.Println(len(b)) //6
    	fmt.Println(cap(b)) //cap(b)=8,底层数组发生扩展
    	
    	d := make([]int, 2, 2)
    	copy(d, c)          //copy只会复制d和c中长度最小的
    	fmt.Println(d)      //[0 1]
    	fmt.Println(len(d)) //2
    	fmt.Println(cap(d)) //2
    

    注意:
    向切片增加元素时,切片的容量会自动增长。1024 以下时,一两倍方式增长。

    • 字符串和切片的相关转换
    str := "hello world"
    a := []byte(str)	///将字符串转换为[]byte类型切片
    b := []tune(str)	//将字符串转换为[]rune类型切片
    

    字典(map)
    Go语言内置的字典类型叫map。map的类型格式是:map[K]T,其中K可以是任意可以进行比较的类型,T是值类型。map也是一种引用类型。

    • map的创建。
    1. 使用字面量创建。
    ma := map[string]int {"a":1,"b":2}
    fmt.Println(ma["a"])
    fmt.Println(ma["b"])
    
    1. 使用内置make创建。
    make(map[k]T)	//map的容量使用默认位
    make(map[K)T , len) //map 的容量使用给定 len 值
    
    mpl := make(map[int]string)
    mp2 := make(map[int]string , 10) 
    
    mpl[1] = "tom"
    mp2[1] = "pony"
    
    • map支持的操作
      map的单个键值访问格式为mapName[key],更新某个key的值时mapName[key]放到等号左边,访问某个key的值时mapName[key]放在等号的右边。
      可以使用range遍历一个map类型变量,但是不保证每次迭代元素的顺序。
      删除map中的某个键值,使用如下语法:delete(mapName,key)。delete是内置函数,用来删除map中的某个键值对。
      可以使用内置的len()函数返回map中的键值对数量。
    	mp := make(map[int]string)
    	mp[1] = "tom"
    	mp[2] = "pony"
    	mp[3] = "jaky"
    
    	delete(mp, 3)
    
    	fmt.Println(len(mp)) //2
    
    	for k, v := range mp {
    		fmt.Println("key=", k, "value=", v)
    	}
    	//key= 1 value= tom
    	//key= 2 value= pony
    

    注意:
    Go内置的map不是并发安全的,并发安全的map可以使用标准包sync中的map。
    不要直接修改map value内某个元素的值,如果想修改map的某个键值,则必须整体赋值。

    	type User struct {
    		name string
    		age  int
    	}
    	ma := make(map[int]User)
    	andes := User{
    		name: "andes",
    		age:  18,
    	}
    	ma[1] = andes
    	//ma[1].age = 19 //ERROR ,不能通过 map 引用直接修改
    	andes.age = 19
    	ma[1] = andes           //必须整体替换 value
    	fmt.Printf(" %v
    ", ma) //map[1:{andes 19}]
    

    结构体(struct)
    Go中的struct类型和C类似,由多个不同类型元素组合而成。这里面有两层含义:
    第一,struct结构中的类型可以是任意类型;
    第二,struct的存储空间是连续的,其字段按照声明时的顺序存放(注意字段之间有对齐要求)。
    struct有两种形式:
    一种是struct 类型字面量;
    另一种是使用type声明的自定义struct类型。

    1. struct类型字面量。
      struct类型字面量的声明格式:
    struct { 
        FeildName FeildType 
        FeildName FeildType 
        FeildName FeildType
    }
    
    1. 自定义 truct 类型
      自定义 struct 类型声明格式如下
    type TypeName struct { 
    	FeildName Fe ldType
    	FeildName FeildType 
    	FeildName FeildType
    }
    

    实际使用struct字面量的场景不多,更多的时候是通过type自定义一个新的类型来实现的。
    type是自定义类型的关键字,不但支持struct类型的创建,还支持任意其他子定义类型的创建。

    	type Person struct {
    		Name string
    		Age  int
    	}
    	
    	type Student struct {
    		*Person
    		Number int
    	}
    	
    	//不推荐这种初始化方式,一旦truct加字段,则整个初始化语句会报
    	a := Person{"Tom", 21}
    	//推荐这种使用 Feild 名字的初始化方式,没有指定的字段则默认初始化为类型的零值
    	p := &Person{
    		Name: "tata",
    		Age:  12,
    	}
    	s := Student{
    		Person: p,
    		Number: 110,
    	}
    

    控制结构

    现代计算机存储结构无论“普林斯顿结构”,还是“哈佛结构”,程序指令都是线性地存放在存储器上。程序执行从本质上来说就是两种模式:顺序和跳转。

    顺序就是按照程序指令在存储器上的存放顺序逐条执行。
    跳转就是遇到跳转指令就跳转到某处继续线性执行。

    顺序在Go里面体现在从main函数开始逐条向下执行,就像我们的程序源代码顺序一样;跳转在Go里面体现为多个语法糖,包括goto语句和函数调用、分支(if、switch、select)、循环(for)等。

    跳转分为两种:一种是无条件跳转,比如函数调用和goto语句;一种是有条件的跳转,比如分支和循环。

    if 语句
    if后面的条件判断子句不需要用小括号括起来。
    {必须放在行尾,和if或if else放在一行。
    if后面可以带一个简单的初始化语句,并以分号分割,该简单语句声明的变量的作用域是整个if语句块,包括后面的else if和else分支。
    Go语言没有条件运算符(a>b?a:b),这也符合Go的设计哲学,只提供一种方法做事情。
    if分支语句遇到return后直接返回,遇到break则跳过break下方的if语句块。

    if x <= y {
    	return y
    } else {
    	return x
    }
    

    完整的if else 实例

    if x := f(); x < y {
    	return x
    } else if x > z {
    	return z
    } else {
    	return y
    }
    

    switch 语句
    switch 语句会根据传入的参数检测井执行符合条件的分支。
    switch和if 语句一样,switch后面可以带一个可选的简单的初始化语句。
    switch后面的表达式也是可选的,如果没有表达式,则case子句是一个布尔表达式,而不是一个值,此时就相当于多重if else语句。
    switch条件表达式的值不像C语言那样必须限制为整数,可以是任意支持相等比较运算的类型变量。
    通过fallthough 语句来强制执行下一个case子句(不再判断下一个case子句的条件是否满足)。
    switch 支持default 语句,当所有的case分支都不符合时,执行default 语句,并且default语句可以放到任意位置,并不影响 switch的判断逻辑。
    switch和.(type)结合可以进行类型的查询,这个放到4.2节介绍。

    switch i := "y"{//switch 后面可以带上一个初始化语
    case "y","Y": //多个 case 值使用逗号分隔
    	fmt.Println ("yes") //yes 
    	fallthrough //fallthrough 会跳过接下来的 case 条件表达式,直接执行下一 case 语句
    case "n","N":  
    	fmt Println("no") //no
    }
    

    附加:
    select 是一种类似于switch语法结构的分支语句。

    for 语句

    Go语言仅支持一种循环语句,即for语句。
    Go对应C循环的三种场景如下。

    1. 类似C里面的for循环语句
    for init;condition;post{ }
    
    1. 类似C里面的while 循环语句
    for condition{ }
    
    1. 类似C里面的while(1)死循环语句
    for{ }
    
    • for 语句对数组、切片、字符串、map、通道的访问
    //访问map
    for key, value := range map {}
    for key := range map {}
    
    //访问数组
    for index, value := range array {}
    for index := range array {}
    for _, value := range array {}
    
    //访问切片
    for index, value := range slice {}
    for index := range slice {}
    for _, value := range slice {}
    
    //访问通道
    for value := range channel {}
    

    标签和跳转

    Go 语言使用标签(Lable)来标识一个语句的位置,用于goto、break、continue 语句的跳转,标签的语法是Lable:Statement

    • goto
      goto语句用于函数的内部的跳转,需要配合标签一起使用,具体的格式如下:
    goto Lable 
    

    goto Lable的语义是跳转到标签名后的语句处执行,goto语句有以下几个特点:

    1. goto语句只能在函数内跳转。
    2. goto语句不能跳过内部变量声明语句,这些变量在goto语句的标签语句处又是可见的。例如:
    goto L //BAD,跳过v:=3这条语句是不允许的
    V:=3
    L:
    

    goto 语句只能跳到同级作用域或者上层作用域内,不能跳到内部作用域内。例如:

    if n%2==1{
    	goto L1
    }
    for n > 0 {
    	f()
    	n--
    }
    L1:
    	fn()
    	n--
    
    • break
      break用于函数内跳出for、switch、select语句的执行,有两种使用格式:
      单独使用,用于跳出break当前所在的for、switch、select语句的执行。
      和标签一起使用,用于跳出标签所标识的for、switch、select语句的执行,可用于跳出多重循环,但标签和break必须在同一个函数内。例如:
    L1:
    	for i := 0; ; i++ {
    		for j := 0; ; j++ {
    			if i >= 5 {
    				break L1 //跳出L1标签所在的for循环break L1
    			}
    			if j > 10 {
    				break //默认仅跳出离break最近的内层循环break
    			}
    		}
    	}
    
    • continue
      continue 用于跳出for循环的本次迭代,跳到for循环的下一次迭代的post语句处执行,也有两种使用格式:
      单独使用,用于跳出continue当前所在的for循环的本次迭代。
      和标签一起使用,用于跳出标签所标识的for 语句的本次迭代,但标签和continue必须在同一个函数内。例如:
    L1:
    	for i := 0; ; i++ {
    		for j := 0; ; j++ {
    			if i >= 5 {
    				continue L1 //跳出L1标签所在的for循环i++处执行
    			}
    
    			if j > 10 {
    				continue //默认仅跳出离continue最近的内层循环j++处执行
    			}
    		}
    	}
    
  • 相关阅读:
    什么是32位汇编的flat平坦内存模式
    oracle随机操作
    网线8根排列顺序
    vb创建NT服务
    函数声明后面加个stdcall是什么意思
    一些基础问题。
    ArcGIS Server中地图打印的实现
    添加BaseCommand 和Base Tool 的注意事项
    获取字符串中的某个子字符串
    AE, C#,按纸张打印地图
  • 原文地址:https://www.cnblogs.com/WindSun/p/12208321.html
Copyright © 2011-2022 走看看